1use std::fmt;
11use std::str::FromStr;
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, EventId};
20
21const TAG_EVENT: &str = "EVENT";
22const TAG_OK: &str = "OK";
23const TAG_EOSE: &str = "EOSE";
24const TAG_CLOSED: &str = "CLOSED";
25const TAG_NOTICE: &str = "NOTICE";
26const TAG_AUTH: &str = "AUTH";
27const TAG_COUNT: &str = "COUNT";
28const TAG_NEG_MSG: &str = "NEG-MSG";
29const TAG_NEG_ERR: &str = "NEG-ERR";
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Error)]
33#[non_exhaustive]
34pub enum MachineReadablePrefixError {
35 #[error("unknown machine-readable prefix")]
37 Unknown,
38}
39
40#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
42#[non_exhaustive]
43pub enum MachineReadablePrefix {
44 Duplicate,
46 Pow,
48 Blocked,
50 RateLimited,
52 Invalid,
54 Error,
56 Restricted,
58 Mute,
61 AuthRequired,
63 PaymentRequired,
65}
66
67impl MachineReadablePrefix {
68 #[must_use]
70 pub const fn as_str(self) -> &'static str {
71 match self {
72 Self::Duplicate => "duplicate",
73 Self::Pow => "pow",
74 Self::Blocked => "blocked",
75 Self::RateLimited => "rate-limited",
76 Self::Invalid => "invalid",
77 Self::Error => "error",
78 Self::Restricted => "restricted",
79 Self::Mute => "mute",
80 Self::AuthRequired => "auth-required",
81 Self::PaymentRequired => "payment-required",
82 }
83 }
84
85 #[must_use]
89 pub fn from_reason(reason: &str) -> Option<Self> {
90 let (prefix, _rest) = reason.split_once(':')?;
91 prefix.parse().ok()
92 }
93}
94
95impl fmt::Display for MachineReadablePrefix {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 f.write_str(self.as_str())
98 }
99}
100
101impl FromStr for MachineReadablePrefix {
102 type Err = MachineReadablePrefixError;
103
104 fn from_str(s: &str) -> Result<Self, Self::Err> {
105 let value = match s {
106 "duplicate" => Self::Duplicate,
107 "pow" => Self::Pow,
108 "blocked" => Self::Blocked,
109 "rate-limited" => Self::RateLimited,
110 "invalid" => Self::Invalid,
111 "error" => Self::Error,
112 "restricted" => Self::Restricted,
113 "mute" => Self::Mute,
114 "auth-required" => Self::AuthRequired,
115 "payment-required" => Self::PaymentRequired,
116 _ => return Err(MachineReadablePrefixError::Unknown),
117 };
118 Ok(value)
119 }
120}
121
122#[derive(Debug, Clone, Error)]
124#[non_exhaustive]
125pub enum RelayMessageError {
126 #[error("relay message must not be empty")]
128 Empty,
129 #[error("unknown relay message tag `{0}`")]
131 UnknownTag(String),
132 #[error("malformed `{tag}` message: {reason}")]
134 Malformed {
135 tag: &'static str,
137 reason: String,
139 },
140}
141
142#[derive(Debug, Clone, PartialEq, Eq)]
144#[non_exhaustive]
145#[allow(
146 clippy::large_enum_variant,
147 reason = "EVENT inherently carries a full Event while the control variants \
148 (OK/EOSE/CLOSED/NOTICE/AUTH) are small; boxing it would add \
149 allocation churn on the relay-send and pool-receive hot paths, \
150 where EVENT is by far the most common message and is moved \
151 straight into/out of the enum. rust-nostr makes the same \
152 trade-off via a Cow<Event> EVENT variant."
153)]
154pub enum RelayMessage {
155 Event {
159 subscription_id: SubscriptionId,
161 event: Event,
163 },
164 Ok {
168 event_id: EventId,
170 accepted: bool,
172 message: String,
175 },
176 EndOfStoredEvents(SubscriptionId),
181 Closed {
185 subscription_id: SubscriptionId,
187 message: String,
190 },
191 Notice(String),
195 Auth(String),
199 Count {
203 subscription_id: SubscriptionId,
205 count: u64,
207 },
208 NegMsg {
213 subscription_id: SubscriptionId,
215 message: String,
217 },
218 NegErr {
222 subscription_id: SubscriptionId,
224 message: String,
228 },
229}
230
231impl Serialize for RelayMessage {
232 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
233 where
234 S: Serializer,
235 {
236 match self {
237 Self::Event {
238 subscription_id,
239 event,
240 } => {
241 let mut seq = serializer.serialize_seq(Some(3))?;
242 seq.serialize_element(TAG_EVENT)?;
243 seq.serialize_element(subscription_id)?;
244 seq.serialize_element(event)?;
245 seq.end()
246 }
247 Self::Ok {
248 event_id,
249 accepted,
250 message,
251 } => {
252 let mut seq = serializer.serialize_seq(Some(4))?;
253 seq.serialize_element(TAG_OK)?;
254 seq.serialize_element(event_id)?;
255 seq.serialize_element(accepted)?;
256 seq.serialize_element(message)?;
257 seq.end()
258 }
259 Self::EndOfStoredEvents(id) => {
260 let mut seq = serializer.serialize_seq(Some(2))?;
261 seq.serialize_element(TAG_EOSE)?;
262 seq.serialize_element(id)?;
263 seq.end()
264 }
265 Self::Closed {
266 subscription_id,
267 message,
268 } => {
269 let mut seq = serializer.serialize_seq(Some(3))?;
270 seq.serialize_element(TAG_CLOSED)?;
271 seq.serialize_element(subscription_id)?;
272 seq.serialize_element(message)?;
273 seq.end()
274 }
275 Self::Notice(message) => {
276 let mut seq = serializer.serialize_seq(Some(2))?;
277 seq.serialize_element(TAG_NOTICE)?;
278 seq.serialize_element(message)?;
279 seq.end()
280 }
281 Self::Auth(challenge) => {
282 let mut seq = serializer.serialize_seq(Some(2))?;
283 seq.serialize_element(TAG_AUTH)?;
284 seq.serialize_element(challenge)?;
285 seq.end()
286 }
287 Self::Count {
288 subscription_id,
289 count,
290 } => {
291 #[derive(Serialize)]
292 struct CountPayload {
293 count: u64,
294 }
295
296 let mut seq = serializer.serialize_seq(Some(3))?;
297 seq.serialize_element(TAG_COUNT)?;
298 seq.serialize_element(subscription_id)?;
299 seq.serialize_element(&CountPayload { count: *count })?;
300 seq.end()
301 }
302 Self::NegMsg {
303 subscription_id,
304 message,
305 } => {
306 let mut seq = serializer.serialize_seq(Some(3))?;
307 seq.serialize_element(TAG_NEG_MSG)?;
308 seq.serialize_element(subscription_id)?;
309 seq.serialize_element(message)?;
310 seq.end()
311 }
312 Self::NegErr {
313 subscription_id,
314 message,
315 } => {
316 let mut seq = serializer.serialize_seq(Some(3))?;
317 seq.serialize_element(TAG_NEG_ERR)?;
318 seq.serialize_element(subscription_id)?;
319 seq.serialize_element(message)?;
320 seq.end()
321 }
322 }
323 }
324}
325
326impl<'de> Deserialize<'de> for RelayMessage {
327 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
328 where
329 D: Deserializer<'de>,
330 {
331 struct RelayVisitor;
332
333 impl<'de> Visitor<'de> for RelayVisitor {
334 type Value = RelayMessage;
335
336 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
337 f.write_str("a Nostr relay message array")
338 }
339
340 fn visit_seq<A>(self, mut seq: A) -> Result<RelayMessage, A::Error>
341 where
342 A: SeqAccess<'de>,
343 {
344 let tag: String = seq
345 .next_element()?
346 .ok_or_else(|| de::Error::custom(RelayMessageError::Empty))?;
347 match tag.as_str() {
348 TAG_EVENT => decode_event(&mut seq),
349 TAG_OK => decode_ok(&mut seq),
350 TAG_EOSE => decode_eose(&mut seq),
351 TAG_CLOSED => decode_closed(&mut seq),
352 TAG_NOTICE => decode_notice(&mut seq),
353 TAG_AUTH => decode_auth(&mut seq),
354 TAG_COUNT => decode_count(&mut seq),
355 TAG_NEG_MSG => decode_neg_msg(&mut seq),
356 TAG_NEG_ERR => decode_neg_err(&mut seq),
357 other => Err(de::Error::custom(RelayMessageError::UnknownTag(
358 other.to_owned(),
359 ))),
360 }
361 }
362 }
363
364 deserializer.deserialize_seq(RelayVisitor)
365 }
366}
367
368fn malformed<E: de::Error>(tag: &'static str, reason: &str) -> E {
369 E::custom(RelayMessageError::Malformed {
370 tag,
371 reason: reason.to_owned(),
372 })
373}
374
375fn decode_event<'de, A>(seq: &mut A) -> Result<RelayMessage, A::Error>
376where
377 A: SeqAccess<'de>,
378{
379 let subscription_id: SubscriptionId = seq
380 .next_element()?
381 .ok_or_else(|| malformed::<A::Error>(TAG_EVENT, "missing subscription id"))?;
382 let event: Event = seq
383 .next_element()?
384 .ok_or_else(|| malformed::<A::Error>(TAG_EVENT, "missing event"))?;
385 Ok(RelayMessage::Event {
386 subscription_id,
387 event,
388 })
389}
390
391fn decode_ok<'de, A>(seq: &mut A) -> Result<RelayMessage, A::Error>
392where
393 A: SeqAccess<'de>,
394{
395 let event_id: EventId = seq
396 .next_element()?
397 .ok_or_else(|| malformed::<A::Error>(TAG_OK, "missing event id"))?;
398 let accepted: bool = seq
399 .next_element()?
400 .ok_or_else(|| malformed::<A::Error>(TAG_OK, "missing accepted flag"))?;
401 let message: String = seq
404 .next_element()?
405 .ok_or_else(|| malformed::<A::Error>(TAG_OK, "missing message"))?;
406 Ok(RelayMessage::Ok {
407 event_id,
408 accepted,
409 message,
410 })
411}
412
413fn decode_eose<'de, A>(seq: &mut A) -> Result<RelayMessage, A::Error>
414where
415 A: SeqAccess<'de>,
416{
417 let id: SubscriptionId = seq
418 .next_element()?
419 .ok_or_else(|| malformed::<A::Error>(TAG_EOSE, "missing subscription id"))?;
420 Ok(RelayMessage::EndOfStoredEvents(id))
421}
422
423fn decode_closed<'de, A>(seq: &mut A) -> Result<RelayMessage, A::Error>
424where
425 A: SeqAccess<'de>,
426{
427 let subscription_id: SubscriptionId = seq
428 .next_element()?
429 .ok_or_else(|| malformed::<A::Error>(TAG_CLOSED, "missing subscription id"))?;
430 let message: String = seq.next_element()?.unwrap_or_default();
431 Ok(RelayMessage::Closed {
432 subscription_id,
433 message,
434 })
435}
436
437fn decode_notice<'de, A>(seq: &mut A) -> Result<RelayMessage, A::Error>
438where
439 A: SeqAccess<'de>,
440{
441 let message: String = seq
442 .next_element()?
443 .ok_or_else(|| malformed::<A::Error>(TAG_NOTICE, "missing message"))?;
444 Ok(RelayMessage::Notice(message))
445}
446
447fn decode_auth<'de, A>(seq: &mut A) -> Result<RelayMessage, A::Error>
448where
449 A: SeqAccess<'de>,
450{
451 let challenge: String = seq
452 .next_element()?
453 .ok_or_else(|| malformed::<A::Error>(TAG_AUTH, "missing challenge"))?;
454 Ok(RelayMessage::Auth(challenge))
455}
456
457fn decode_count<'de, A>(seq: &mut A) -> Result<RelayMessage, A::Error>
458where
459 A: SeqAccess<'de>,
460{
461 #[derive(Deserialize)]
462 struct CountPayload {
463 count: u64,
464 }
465
466 let subscription_id: SubscriptionId = seq
467 .next_element()?
468 .ok_or_else(|| malformed::<A::Error>(TAG_COUNT, "missing subscription id"))?;
469 let payload: CountPayload = seq
470 .next_element()?
471 .ok_or_else(|| malformed::<A::Error>(TAG_COUNT, "missing count payload"))?;
472 Ok(RelayMessage::Count {
473 subscription_id,
474 count: payload.count,
475 })
476}
477
478fn decode_neg_msg<'de, A>(seq: &mut A) -> Result<RelayMessage, A::Error>
479where
480 A: SeqAccess<'de>,
481{
482 let subscription_id: SubscriptionId = seq
483 .next_element()?
484 .ok_or_else(|| malformed::<A::Error>(TAG_NEG_MSG, "missing subscription id"))?;
485 let message: String = seq
486 .next_element()?
487 .ok_or_else(|| malformed::<A::Error>(TAG_NEG_MSG, "missing message"))?;
488 Ok(RelayMessage::NegMsg {
489 subscription_id,
490 message,
491 })
492}
493
494fn decode_neg_err<'de, A>(seq: &mut A) -> Result<RelayMessage, A::Error>
495where
496 A: SeqAccess<'de>,
497{
498 let subscription_id: SubscriptionId = seq
499 .next_element()?
500 .ok_or_else(|| malformed::<A::Error>(TAG_NEG_ERR, "missing subscription id"))?;
501 let message: String = seq
502 .next_element()?
503 .ok_or_else(|| malformed::<A::Error>(TAG_NEG_ERR, "missing message"))?;
504 Ok(RelayMessage::NegErr {
505 subscription_id,
506 message,
507 })
508}
509
510#[cfg(test)]
511mod tests {
512 use super::*;
513 use crate::Keys;
514 use crate::event::EventBuilder;
515
516 fn keys() -> Keys {
517 Keys::parse("0000000000000000000000000000000000000000000000000000000000000003").unwrap()
518 }
519
520 fn signed_event() -> Event {
521 EventBuilder::text_note("hello")
522 .sign_with_keys(&keys())
523 .unwrap()
524 }
525
526 fn sub() -> SubscriptionId {
527 SubscriptionId::new("sub-1").unwrap()
528 }
529
530 #[test]
531 fn event_round_trip() {
532 let msg = RelayMessage::Event {
533 subscription_id: sub(),
534 event: signed_event(),
535 };
536 let json = serde_json::to_string(&msg).unwrap();
537 assert!(json.starts_with("[\"EVENT\",\"sub-1\","));
538 let parsed: RelayMessage = serde_json::from_str(&json).unwrap();
539 assert_eq!(parsed, msg);
540 }
541
542 #[test]
543 fn ok_round_trip_with_message() {
544 let msg = RelayMessage::Ok {
545 event_id: signed_event().id,
546 accepted: false,
547 message: "blocked: spam".to_owned(),
548 };
549 let json = serde_json::to_string(&msg).unwrap();
550 let parsed: RelayMessage = serde_json::from_str(&json).unwrap();
551 assert_eq!(parsed, msg);
552 }
553
554 #[test]
555 fn ok_with_empty_message_round_trip() {
556 let msg = RelayMessage::Ok {
557 event_id: signed_event().id,
558 accepted: true,
559 message: String::new(),
560 };
561 let json = serde_json::to_string(&msg).unwrap();
562 let parsed: RelayMessage = serde_json::from_str(&json).unwrap();
563 assert_eq!(parsed, msg);
564 }
565
566 #[test]
567 fn eose_round_trip() {
568 let msg = RelayMessage::EndOfStoredEvents(sub());
569 let json = serde_json::to_string(&msg).unwrap();
570 assert_eq!(json, "[\"EOSE\",\"sub-1\"]");
571 let parsed: RelayMessage = serde_json::from_str(&json).unwrap();
572 assert_eq!(parsed, msg);
573 }
574
575 #[test]
576 fn closed_round_trip() {
577 let msg = RelayMessage::Closed {
578 subscription_id: sub(),
579 message: "auth-required: please authenticate".to_owned(),
580 };
581 let json = serde_json::to_string(&msg).unwrap();
582 let parsed: RelayMessage = serde_json::from_str(&json).unwrap();
583 assert_eq!(parsed, msg);
584 }
585
586 #[test]
587 fn notice_round_trip() {
588 let msg = RelayMessage::Notice("welcome".to_owned());
589 let json = serde_json::to_string(&msg).unwrap();
590 assert_eq!(json, "[\"NOTICE\",\"welcome\"]");
591 let parsed: RelayMessage = serde_json::from_str(&json).unwrap();
592 assert_eq!(parsed, msg);
593 }
594
595 #[test]
596 fn auth_challenge_round_trip() {
597 let msg = RelayMessage::Auth("challenge-string".to_owned());
598 let json = serde_json::to_string(&msg).unwrap();
599 let parsed: RelayMessage = serde_json::from_str(&json).unwrap();
600 assert_eq!(parsed, msg);
601 }
602
603 #[test]
604 fn count_round_trip() {
605 let msg = RelayMessage::Count {
606 subscription_id: sub(),
607 count: 42,
608 };
609 let json = serde_json::to_string(&msg).unwrap();
610 assert_eq!(json, "[\"COUNT\",\"sub-1\",{\"count\":42}]");
611 let parsed: RelayMessage = serde_json::from_str(&json).unwrap();
612 assert_eq!(parsed, msg);
613 }
614
615 #[test]
616 fn machine_readable_prefix_parses() {
617 assert_eq!(
618 MachineReadablePrefix::from_reason("blocked: spam"),
619 Some(MachineReadablePrefix::Blocked)
620 );
621 assert_eq!(
622 MachineReadablePrefix::from_reason("auth-required: please"),
623 Some(MachineReadablePrefix::AuthRequired)
624 );
625 assert_eq!(
626 MachineReadablePrefix::from_reason("mute: nobody listening"),
627 Some(MachineReadablePrefix::Mute)
628 );
629 assert!(MachineReadablePrefix::from_reason("no prefix").is_none());
630 assert!(MachineReadablePrefix::from_reason("unknown: thing").is_none());
631 }
632
633 #[test]
634 fn ok_round_trips_mute_prefix() {
635 let msg = RelayMessage::Ok {
636 event_id: signed_event().id,
637 accepted: false,
638 message: "mute: nobody was listening".to_owned(),
639 };
640 let json = serde_json::to_string(&msg).unwrap();
641 let parsed: RelayMessage = serde_json::from_str(&json).unwrap();
642 assert_eq!(parsed, msg);
643 }
644
645 #[test]
646 fn ok_rejects_missing_message_per_nip01() {
647 let json = format!(r#"["OK","{}",true]"#, signed_event().id.to_hex());
649 let err = serde_json::from_str::<RelayMessage>(&json).unwrap_err();
650 assert!(err.to_string().contains("missing message"));
651 }
652
653 #[test]
654 fn unknown_tag_rejected() {
655 let json = "[\"WAT\",\"x\"]";
656 let err = serde_json::from_str::<RelayMessage>(json).unwrap_err();
657 assert!(err.to_string().contains("unknown relay message tag"));
658 }
659
660 #[test]
661 fn neg_msg_round_trip() {
662 let msg = RelayMessage::NegMsg {
663 subscription_id: sub(),
664 message: "deadbeef".to_owned(),
665 };
666 let json = serde_json::to_string(&msg).unwrap();
667 assert_eq!(json, "[\"NEG-MSG\",\"sub-1\",\"deadbeef\"]");
668 let parsed: RelayMessage = serde_json::from_str(&json).unwrap();
669 assert_eq!(parsed, msg);
670 }
671
672 #[test]
673 fn neg_err_round_trip() {
674 let msg = RelayMessage::NegErr {
675 subscription_id: sub(),
676 message: "blocked: spam".to_owned(),
677 };
678 let json = serde_json::to_string(&msg).unwrap();
679 let parsed: RelayMessage = serde_json::from_str(&json).unwrap();
680 assert_eq!(parsed, msg);
681 }
682
683 #[test]
684 fn neg_msg_missing_payload_rejected() {
685 let json = "[\"NEG-MSG\",\"sub-1\"]";
686 let err = serde_json::from_str::<RelayMessage>(json).unwrap_err();
687 assert!(err.to_string().contains("missing message"));
688 }
689}