1use crate::{DelegationConditions, Id, PublicKeyHex, SignatureHex, UncheckedUrl, Unixtime};
2use serde::de::{Deserialize, Deserializer, SeqAccess, Visitor};
3use serde::ser::{Serialize, SerializeSeq, Serializer};
4use std::fmt;
5
6#[derive(Clone, Debug, Eq, PartialEq)]
8pub enum Tag {
9 ContentWarning(String),
11
12 Delegation {
14 pubkey: PublicKeyHex,
16
17 conditions: DelegationConditions,
19
20 sig: SignatureHex,
22 },
23
24 Event {
28 id: Id,
30
31 recommended_relay_url: Option<UncheckedUrl>,
33
34 marker: Option<String>,
36 },
37
38 Expiration(Unixtime),
40
41 Pubkey {
45 pubkey: PublicKeyHex,
47
48 recommended_relay_url: Option<UncheckedUrl>,
50
51 petname: Option<String>,
53 },
54
55 Hashtag(String),
57
58 Reference {
60 url: UncheckedUrl,
62
63 marker: Option<String>,
65 },
66
67 Geohash(String),
69
70 Subject(String),
72
73 Nonce {
75 nonce: String,
77
78 target: Option<String>,
80 },
81
82 Parameter(String),
84
85 Other {
87 tag: String,
89
90 data: Vec<String>,
92 },
93
94 Empty,
96}
97
98impl Tag {
99 pub fn tagname(&self) -> String {
101 match self {
102 Tag::ContentWarning(_) => "content-warning".to_string(),
103 Tag::Delegation { .. } => "delegation".to_string(),
104 Tag::Event { .. } => "e".to_string(),
105 Tag::Expiration(_) => "expiration".to_string(),
106 Tag::Pubkey { .. } => "p".to_string(),
107 Tag::Hashtag(_) => "t".to_string(),
108 Tag::Reference { .. } => "r".to_string(),
109 Tag::Geohash(_) => "g".to_string(),
110 Tag::Subject(_) => "subject".to_string(),
111 Tag::Nonce { .. } => "nonce".to_string(),
112 Tag::Parameter(_) => "parameter".to_string(),
113 Tag::Other { tag, .. } => tag.clone(),
114 Tag::Empty => panic!("empty tags have no tagname"),
115 }
116 }
117
118 #[allow(dead_code)]
120 pub(crate) fn mock() -> Tag {
121 Tag::Event {
122 id: Id::mock(),
123 recommended_relay_url: Some(UncheckedUrl::mock()),
124 marker: None,
125 }
126 }
127}
128
129impl Serialize for Tag {
130 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
131 where
132 S: Serializer,
133 {
134 match self {
135 Tag::ContentWarning(msg) => {
136 let mut seq = serializer.serialize_seq(None)?;
137 seq.serialize_element("content-warning")?;
138 seq.serialize_element(msg)?;
139 seq.end()
140 }
141 Tag::Delegation {
142 pubkey,
143 conditions,
144 sig,
145 } => {
146 let mut seq = serializer.serialize_seq(None)?;
147 seq.serialize_element("delegation")?;
148 seq.serialize_element(pubkey)?;
149 seq.serialize_element(conditions)?;
150 seq.serialize_element(sig)?;
151 seq.end()
152 }
153 Tag::Event {
154 id,
155 recommended_relay_url,
156 marker,
157 } => {
158 let mut seq = serializer.serialize_seq(None)?;
159 seq.serialize_element("e")?;
160 seq.serialize_element(id)?;
161 if let Some(rru) = recommended_relay_url {
162 seq.serialize_element(rru)?;
163 } else if marker.is_some() {
164 seq.serialize_element("")?;
165 }
166 if let Some(m) = marker {
167 seq.serialize_element(m)?;
168 }
169 seq.end()
170 }
171 Tag::Expiration(time) => {
172 let mut seq = serializer.serialize_seq(None)?;
173 seq.serialize_element("expiration")?;
174 seq.serialize_element(time)?;
175 seq.end()
176 }
177 Tag::Pubkey {
178 pubkey,
179 recommended_relay_url,
180 petname,
181 } => {
182 let mut seq = serializer.serialize_seq(None)?;
183 seq.serialize_element("p")?;
184 seq.serialize_element(pubkey)?;
185 if let Some(rru) = recommended_relay_url {
186 seq.serialize_element(rru)?;
187 } else if petname.is_some() {
188 seq.serialize_element("")?;
189 }
190 if let Some(pn) = petname {
191 seq.serialize_element(pn)?;
192 }
193 seq.end()
194 }
195 Tag::Hashtag(hashtag) => {
196 let mut seq = serializer.serialize_seq(None)?;
197 seq.serialize_element("t")?;
198 seq.serialize_element(hashtag)?;
199 seq.end()
200 }
201 Tag::Reference { url, marker } => {
202 let mut seq = serializer.serialize_seq(None)?;
203 seq.serialize_element("r")?;
204 seq.serialize_element(url)?;
205 if let Some(m) = marker {
206 seq.serialize_element(m)?
207 }
208 seq.end()
209 }
210 Tag::Geohash(geohash) => {
211 let mut seq = serializer.serialize_seq(None)?;
212 seq.serialize_element("g")?;
213 seq.serialize_element(geohash)?;
214 seq.end()
215 }
216 Tag::Subject(subject) => {
217 let mut seq = serializer.serialize_seq(None)?;
218 seq.serialize_element("subject")?;
219 seq.serialize_element(subject)?;
220 seq.end()
221 }
222 Tag::Nonce { nonce, target } => {
223 let mut seq = serializer.serialize_seq(None)?;
224 seq.serialize_element("nonce")?;
225 seq.serialize_element(nonce)?;
226 if let Some(t) = target {
227 seq.serialize_element(t)?;
228 }
229 seq.end()
230 }
231 Tag::Parameter(parameter) => {
232 let mut seq = serializer.serialize_seq(None)?;
233 seq.serialize_element("parameter")?;
234 seq.serialize_element(parameter)?;
235 seq.end()
236 }
237 Tag::Other { tag, data } => {
238 let mut seq = serializer.serialize_seq(None)?;
239 seq.serialize_element(tag)?;
240 for s in data.iter() {
241 seq.serialize_element(s)?;
242 }
243 seq.end()
244 }
245 Tag::Empty => {
246 let seq = serializer.serialize_seq(Some(0))?;
247 seq.end()
248 }
249 }
250 }
251}
252
253impl<'de> Deserialize<'de> for Tag {
254 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
255 where
256 D: Deserializer<'de>,
257 {
258 deserializer.deserialize_seq(TagVisitor)
259 }
260}
261
262struct TagVisitor;
263
264impl<'de> Visitor<'de> for TagVisitor {
265 type Value = Tag;
266
267 fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
268 write!(f, "a sequence of strings")
269 }
270
271 fn visit_seq<A>(self, mut seq: A) -> Result<Tag, A::Error>
272 where
273 A: SeqAccess<'de>,
274 {
275 let tagname: &str = match seq.next_element()? {
276 Some(e) => e,
277 None => return Ok(Tag::Empty),
278 };
279 if tagname == "content-warning" {
280 let msg = match seq.next_element()? {
281 Some(s) => s,
282 None => {
283 return Ok(Tag::Other {
284 tag: tagname.to_string(),
285 data: vec![],
286 });
287 }
288 };
289 Ok(Tag::ContentWarning(msg))
290 } else if tagname == "delegation" {
291 let pubkey: PublicKeyHex = match seq.next_element()? {
292 Some(pk) => pk,
293 None => {
294 return Ok(Tag::Other {
295 tag: tagname.to_string(),
296 data: vec![],
297 });
298 }
299 };
300 let conditions: DelegationConditions = match seq.next_element()? {
301 Some(c) => c,
302 None => {
303 return Ok(Tag::Other {
304 tag: tagname.to_string(),
305 data: vec![pubkey.into_string()],
306 });
307 }
308 };
309 let sig: SignatureHex = match seq.next_element()? {
310 Some(s) => s,
311 None => {
312 return Ok(Tag::Other {
313 tag: tagname.to_string(),
314 data: vec![pubkey.into_string(), conditions.as_string()],
315 });
316 }
317 };
318 Ok(Tag::Delegation {
319 pubkey,
320 conditions,
321 sig,
322 })
323 } else if tagname == "e" {
324 let id: Id = match seq.next_element()? {
325 Some(id) => id,
326 None => {
327 return Ok(Tag::Other {
328 tag: tagname.to_string(),
329 data: vec![],
330 });
331 }
332 };
333 let recommended_relay_url: Option<UncheckedUrl> = seq.next_element()?;
334 let marker: Option<String> = seq.next_element()?;
335 Ok(Tag::Event {
336 id,
337 recommended_relay_url,
338 marker,
339 })
340 } else if tagname == "expiration" {
341 let time = match seq.next_element()? {
342 Some(t) => t,
343 None => {
344 return Ok(Tag::Other {
345 tag: tagname.to_string(),
346 data: vec![],
347 });
348 }
349 };
350 Ok(Tag::Expiration(time))
351 } else if tagname == "p" {
352 let pubkey: PublicKeyHex = match seq.next_element()? {
353 Some(p) => p,
354 None => {
355 return Ok(Tag::Other {
356 tag: tagname.to_string(),
357 data: vec![],
358 });
359 }
360 };
361 let recommended_relay_url: Option<UncheckedUrl> = seq.next_element()?;
362 let petname: Option<String> = seq.next_element()?;
363 Ok(Tag::Pubkey {
364 pubkey,
365 recommended_relay_url,
366 petname,
367 })
368 } else if tagname == "t" {
369 let tag = match seq.next_element()? {
370 Some(t) => t,
371 None => {
372 return Ok(Tag::Other {
373 tag: tagname.to_string(),
374 data: vec![],
375 });
376 }
377 };
378 Ok(Tag::Hashtag(tag))
379 } else if tagname == "r" {
380 let refr: UncheckedUrl = match seq.next_element()? {
381 Some(r) => r,
382 None => {
383 return Ok(Tag::Other {
384 tag: tagname.to_string(),
385 data: vec![],
386 });
387 }
388 };
389 let marker: Option<String> = seq.next_element()?;
390 Ok(Tag::Reference { url: refr, marker })
391 } else if tagname == "g" {
392 let geo = match seq.next_element()? {
393 Some(g) => g,
394 None => {
395 return Ok(Tag::Other {
396 tag: tagname.to_string(),
397 data: vec![],
398 });
399 }
400 };
401 Ok(Tag::Geohash(geo))
402 } else if tagname == "subject" {
403 let sub = match seq.next_element()? {
404 Some(s) => s,
405 None => {
406 return Ok(Tag::Other {
407 tag: tagname.to_string(),
408 data: vec![],
409 });
410 }
411 };
412 Ok(Tag::Subject(sub))
413 } else if tagname == "nonce" {
414 let nonce = match seq.next_element()? {
415 Some(n) => n,
416 None => {
417 return Ok(Tag::Other {
418 tag: tagname.to_string(),
419 data: vec![],
420 });
421 }
422 };
423 let target: Option<String> = seq.next_element()?;
424 Ok(Tag::Nonce { nonce, target })
425 } else if tagname == "parameter" {
426 let param = match seq.next_element()? {
427 Some(s) => s,
428 None => "".to_owned(), };
430 Ok(Tag::Parameter(param))
431 } else {
432 let mut data = Vec::new();
433 loop {
434 match seq.next_element()? {
435 None => {
436 return Ok(Tag::Other {
437 tag: tagname.to_string(),
438 data,
439 })
440 }
441 Some(s) => data.push(s),
442 }
443 }
444 }
445 }
446}
447
448#[cfg(test)]
449mod test {
450 use super::*;
451
452 test_serde! {Tag, test_tag_serde}
453}