1use thiserror::Error;
15
16use crate::event::{Event, EventBuilder, Kind, Tag, TagKind};
17use crate::types::{RelayUrl, RelayUrlError};
18
19pub const KIND_REQUEST_TO_VANISH: Kind = Kind::REQUEST_TO_VANISH;
21
22pub const ALL_RELAYS_SENTINEL: &str = "ALL_RELAYS";
24
25const RELAY_TAG: &str = "relay";
26
27#[derive(Debug, Clone, PartialEq, Eq, Hash)]
29pub enum VanishTarget {
30 Relay(RelayUrl),
32 AllRelays,
35}
36
37#[derive(Debug, Clone, PartialEq, Eq)]
39pub struct RequestToVanish {
40 pub reason: String,
42 pub targets: Vec<VanishTarget>,
44 pub extra_tags: Vec<Tag>,
46}
47
48#[derive(Debug, Error)]
50#[non_exhaustive]
51pub enum VanishError {
52 #[error("unexpected kind for NIP-62 request to vanish: {}", .0.as_u16())]
54 WrongKind(Kind),
55 #[error("NIP-62 event missing required `relay` tag")]
57 MissingTarget,
58 #[error("`relay` tag missing target")]
60 MalformedTarget,
61 #[error(transparent)]
63 InvalidRelayUrl(#[from] RelayUrlError),
64}
65
66impl VanishTarget {
67 fn to_tag(&self) -> Tag {
68 let head = TagKind::from_wire(RELAY_TAG);
69 match self {
70 Self::Relay(url) => Tag::with(&head, [url.as_str().to_owned()]),
71 Self::AllRelays => Tag::with(&head, [ALL_RELAYS_SENTINEL.to_owned()]),
72 }
73 }
74
75 fn from_tag(tag: &Tag) -> Result<Self, VanishError> {
76 let raw = tag.get(1).ok_or(VanishError::MalformedTarget)?;
77 if raw == ALL_RELAYS_SENTINEL {
78 Ok(Self::AllRelays)
79 } else {
80 Ok(Self::Relay(RelayUrl::parse(raw)?))
81 }
82 }
83}
84
85impl RequestToVanish {
86 #[must_use]
88 pub fn relay(reason: impl Into<String>, relays: Vec<RelayUrl>) -> Self {
89 Self {
90 reason: reason.into(),
91 targets: relays.into_iter().map(VanishTarget::Relay).collect(),
92 extra_tags: Vec::new(),
93 }
94 }
95
96 #[must_use]
98 pub fn all_relays(reason: impl Into<String>) -> Self {
99 Self {
100 reason: reason.into(),
101 targets: vec![VanishTarget::AllRelays],
102 extra_tags: Vec::new(),
103 }
104 }
105
106 pub fn from_event(event: &Event) -> Result<Self, VanishError> {
112 if event.kind != KIND_REQUEST_TO_VANISH {
113 return Err(VanishError::WrongKind(event.kind));
114 }
115 let mut targets: Vec<VanishTarget> = Vec::new();
116 let mut extra_tags: Vec<Tag> = Vec::new();
117 for tag in &event.tags {
118 if tag.name() == RELAY_TAG {
119 targets.push(VanishTarget::from_tag(tag)?);
120 } else {
121 extra_tags.push(tag.clone());
122 }
123 }
124 if targets.is_empty() {
125 return Err(VanishError::MissingTarget);
126 }
127 Ok(Self {
128 reason: event.content.clone(),
129 targets,
130 extra_tags,
131 })
132 }
133}
134
135impl EventBuilder {
136 pub fn request_to_vanish(req: &RequestToVanish) -> Result<Self, VanishError> {
143 if req.targets.is_empty() {
144 return Err(VanishError::MissingTarget);
145 }
146 let mut builder = Self::new(KIND_REQUEST_TO_VANISH, req.reason.clone());
147 for target in &req.targets {
148 builder = builder.tag(target.to_tag());
149 }
150 for tag in &req.extra_tags {
151 builder = builder.tag(tag.clone());
152 }
153 Ok(builder)
154 }
155}
156
157#[cfg(test)]
158mod tests {
159 use super::*;
160 use crate::Keys;
161
162 fn keys() -> Keys {
163 Keys::parse("0000000000000000000000000000000000000000000000000000000000000003").unwrap()
164 }
165
166 #[test]
167 fn relay_target_round_trip() {
168 let req = RequestToVanish::relay(
169 "GDPR request",
170 vec![RelayUrl::parse("wss://relay.example/").unwrap()],
171 );
172 let event = EventBuilder::request_to_vanish(&req)
173 .unwrap()
174 .sign_with_keys(&keys())
175 .unwrap();
176 let parsed = RequestToVanish::from_event(&event).unwrap();
177 assert_eq!(parsed, req);
178 }
179
180 #[test]
181 fn all_relays_round_trip() {
182 let req = RequestToVanish::all_relays("legal");
183 let event = EventBuilder::request_to_vanish(&req)
184 .unwrap()
185 .sign_with_keys(&keys())
186 .unwrap();
187 let parsed = RequestToVanish::from_event(&event).unwrap();
188 assert!(matches!(parsed.targets[0], VanishTarget::AllRelays));
189 }
190
191 #[test]
192 fn missing_target_is_rejected() {
193 let req = RequestToVanish {
194 reason: "x".into(),
195 targets: Vec::new(),
196 extra_tags: Vec::new(),
197 };
198 assert!(matches!(
199 EventBuilder::request_to_vanish(&req),
200 Err(VanishError::MissingTarget)
201 ));
202 }
203
204 #[test]
205 fn wrong_kind_is_rejected() {
206 let event = EventBuilder::text_note("nope")
208 .sign_with_keys(&keys())
209 .unwrap();
210 assert!(matches!(
211 RequestToVanish::from_event(&event),
212 Err(VanishError::WrongKind(_))
213 ));
214 }
215
216 #[test]
217 fn parse_event_with_no_relay_tags_rejected() {
218 let event = EventBuilder::new(KIND_REQUEST_TO_VANISH, "no relay tag here")
220 .sign_with_keys(&keys())
221 .unwrap();
222 assert!(matches!(
223 RequestToVanish::from_event(&event),
224 Err(VanishError::MissingTarget)
225 ));
226 }
227
228 #[test]
229 fn malformed_relay_tag_is_rejected() {
230 let event = EventBuilder::new(KIND_REQUEST_TO_VANISH, "")
233 .tag(Tag::with(
234 &TagKind::from_wire(RELAY_TAG),
235 Vec::<String>::new(),
236 ))
237 .sign_with_keys(&keys())
238 .unwrap();
239 assert!(matches!(
240 RequestToVanish::from_event(&event),
241 Err(VanishError::MalformedTarget)
242 ));
243 }
244}