atom_syndication/entry.rs
1use std::borrow::Cow;
2use std::io::{BufRead, Write};
3
4use quick_xml::events::attributes::Attributes;
5use quick_xml::events::{BytesEnd, BytesStart, Event};
6use quick_xml::Reader;
7use quick_xml::Writer;
8
9use crate::category::Category;
10use crate::content::Content;
11use crate::error::{Error, XmlError};
12use crate::extension::util::{extension_name, parse_extension};
13use crate::extension::ExtensionMap;
14use crate::fromxml::FromXml;
15use crate::link::Link;
16use crate::person::Person;
17use crate::source::Source;
18use crate::text::Text;
19use crate::toxml::{ToXml, WriterExt};
20use crate::util::{atom_datetime, atom_text, decode, default_fixed_datetime, skip, FixedDateTime};
21
22/// Represents an entry in an Atom feed
23#[cfg_attr(feature = "serde", derive(Deserialize, Serialize))]
24#[derive(Debug, Clone, PartialEq)]
25#[cfg_attr(feature = "builders", derive(Builder))]
26#[cfg_attr(
27 feature = "builders",
28 builder(
29 setter(into),
30 default,
31 build_fn(name = "build_impl", private, error = "never::Never")
32 )
33)]
34pub struct Entry {
35 /// A human-readable title for the entry.
36 pub title: Text,
37 /// A universally unique and permanent URI.
38 pub id: String,
39 /// The last time the entry was modified.
40 pub updated: FixedDateTime,
41 /// The authors of the feed.
42 #[cfg_attr(feature = "builders", builder(setter(each = "author")))]
43 pub authors: Vec<Person>,
44 /// The categories that the entry belongs to.
45 #[cfg_attr(feature = "builders", builder(setter(each = "category")))]
46 pub categories: Vec<Category>,
47 /// The contributors to the entry.
48 #[cfg_attr(feature = "builders", builder(setter(each = "contributor")))]
49 pub contributors: Vec<Person>,
50 /// The Web pages related to the entry.
51 #[cfg_attr(feature = "builders", builder(setter(each = "link")))]
52 pub links: Vec<Link>,
53 /// The time of the initial creation or first availability of the entry.
54 pub published: Option<FixedDateTime>,
55 /// Information about rights held in and over the entry.
56 pub rights: Option<Text>,
57 /// The source information if an entry is copied from one feed into another feed.
58 pub source: Option<Source>,
59 /// A short summary, abstract, or excerpt of the entry.
60 pub summary: Option<Text>,
61 /// Contains or links to the complete content of the entry.
62 pub content: Option<Content>,
63 /// The extensions for this entry.
64 #[cfg_attr(feature = "builders", builder(setter(each = "extension")))]
65 pub extensions: ExtensionMap,
66}
67
68impl Entry {
69 /// Return the title of this entry.
70 ///
71 /// # Examples
72 ///
73 /// ```
74 /// use atom_syndication::Entry;
75 ///
76 /// let mut entry = Entry::default();
77 /// entry.set_title("Entry Title");
78 /// assert_eq!(entry.title(), "Entry Title");
79 /// ```
80 pub fn title(&self) -> &Text {
81 &self.title
82 }
83
84 /// Set the title of this entry.
85 ///
86 /// # Examples
87 ///
88 /// ```
89 /// use atom_syndication::Entry;
90 ///
91 /// let mut entry = Entry::default();
92 /// entry.set_title("Entry Title");
93 /// ```
94 pub fn set_title<V>(&mut self, title: V)
95 where
96 V: Into<Text>,
97 {
98 self.title = title.into();
99 }
100
101 /// Return the unique URI of this entry.
102 ///
103 /// # Examples
104 ///
105 /// ```
106 /// use atom_syndication::Entry;
107 ///
108 /// let mut entry = Entry::default();
109 /// entry.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6");
110 /// assert_eq!(entry.id(), "urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6");
111 /// ```
112 pub fn id(&self) -> &str {
113 self.id.as_str()
114 }
115
116 /// Set the unique URI of this entry.
117 ///
118 /// # Examples
119 ///
120 /// ```
121 /// use atom_syndication::Entry;
122 ///
123 /// let mut entry = Entry::default();
124 /// entry.set_id("urn:uuid:60a76c80-d399-11d9-b91C-0003939e0af6");
125 /// ```
126 pub fn set_id<V>(&mut self, id: V)
127 where
128 V: Into<String>,
129 {
130 self.id = id.into();
131 }
132
133 /// Return the last time that this entry was modified.
134 ///
135 /// # Examples
136 ///
137 /// ```
138 /// use atom_syndication::Entry;
139 /// use atom_syndication::FixedDateTime;
140 /// use std::str::FromStr;
141 ///
142 /// let mut entry = Entry::default();
143 /// entry.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap());
144 /// assert_eq!(entry.updated().to_rfc3339(), "2017-06-03T15:15:44-05:00");
145 /// ```
146 pub fn updated(&self) -> &FixedDateTime {
147 &self.updated
148 }
149
150 /// Set the last time that this entry was modified.
151 ///
152 /// # Examples
153 ///
154 /// ```
155 /// use atom_syndication::Entry;
156 /// use atom_syndication::FixedDateTime;
157 /// use std::str::FromStr;
158 ///
159 /// let mut entry = Entry::default();
160 /// entry.set_updated(FixedDateTime::from_str("2017-06-03T15:15:44-05:00").unwrap());
161 /// ```
162 pub fn set_updated<V>(&mut self, updated: V)
163 where
164 V: Into<FixedDateTime>,
165 {
166 self.updated = updated.into();
167 }
168
169 /// Return the authors of this entry.
170 ///
171 /// # Examples
172 ///
173 /// ```
174 /// use atom_syndication::{Entry, Person};
175 ///
176 /// let mut entry = Entry::default();
177 /// entry.set_authors(vec![Person::default()]);
178 /// assert_eq!(entry.authors().len(), 1);
179 /// ```
180 pub fn authors(&self) -> &[Person] {
181 self.authors.as_slice()
182 }
183
184 /// Set the authors of this entry.
185 ///
186 /// # Examples
187 ///
188 /// ```
189 /// use atom_syndication::{Entry, Person};
190 ///
191 /// let mut entry = Entry::default();
192 /// entry.set_authors(vec![Person::default()]);
193 /// ```
194 pub fn set_authors<V>(&mut self, authors: V)
195 where
196 V: Into<Vec<Person>>,
197 {
198 self.authors = authors.into();
199 }
200
201 /// Return the categories this entry belongs to.
202 ///
203 /// # Examples
204 ///
205 /// ```
206 /// use atom_syndication::{Entry, Category};
207 ///
208 /// let mut entry = Entry::default();
209 /// entry.set_categories(vec![Category::default()]);
210 /// assert_eq!(entry.categories().len(), 1);
211 /// ```
212 pub fn categories(&self) -> &[Category] {
213 self.categories.as_slice()
214 }
215
216 /// Set the categories this entry belongs to.
217 ///
218 /// # Examples
219 ///
220 /// ```
221 /// use atom_syndication::{Entry, Category};
222 ///
223 /// let mut entry = Entry::default();
224 /// entry.set_categories(vec![Category::default()]);
225 /// ```
226 pub fn set_categories<V>(&mut self, categories: V)
227 where
228 V: Into<Vec<Category>>,
229 {
230 self.categories = categories.into();
231 }
232
233 /// Return the contributors to this entry.
234 ///
235 /// # Examples
236 ///
237 /// ```
238 /// use atom_syndication::{Entry, Person};
239 ///
240 /// let mut entry = Entry::default();
241 /// entry.set_contributors(vec![Person::default()]);
242 /// assert_eq!(entry.contributors().len(), 1);
243 /// ```
244 pub fn contributors(&self) -> &[Person] {
245 self.contributors.as_slice()
246 }
247
248 /// Set the contributors to this entry.
249 ///
250 /// # Examples
251 ///
252 /// ```
253 /// use atom_syndication::{Entry, Person};
254 ///
255 /// let mut entry = Entry::default();
256 /// entry.set_contributors(vec![Person::default()]);
257 /// ```
258 pub fn set_contributors<V>(&mut self, contributors: V)
259 where
260 V: Into<Vec<Person>>,
261 {
262 self.contributors = contributors.into();
263 }
264
265 /// Return the links for this entry.
266 ///
267 /// # Examples
268 ///
269 /// ```
270 /// use atom_syndication::{Entry, Link};
271 ///
272 /// let mut entry = Entry::default();
273 /// entry.set_links(vec![Link::default()]);
274 /// assert_eq!(entry.links().len(), 1);
275 /// ```
276 pub fn links(&self) -> &[Link] {
277 self.links.as_slice()
278 }
279
280 /// Set the links for this entry.
281 ///
282 /// # Examples
283 ///
284 /// ```
285 /// use atom_syndication::{Entry, Link};
286 ///
287 /// let mut entry = Entry::default();
288 /// entry.set_links(vec![Link::default()]);
289 /// ```
290 pub fn set_links<V>(&mut self, links: V)
291 where
292 V: Into<Vec<Link>>,
293 {
294 self.links = links.into();
295 }
296
297 /// Return the time that this entry was initially created or first made available.
298 ///
299 /// # Examples
300 ///
301 /// ```
302 /// use atom_syndication::Entry;
303 /// use atom_syndication::FixedDateTime;
304 /// use std::str::FromStr;
305 ///
306 /// let mut entry = Entry::default();
307 /// entry.set_published(FixedDateTime::from_str("2017-06-01T15:15:44-05:00").unwrap());
308 /// assert_eq!(entry.published().map(|x|x.to_rfc3339()), Some("2017-06-01T15:15:44-05:00".to_string()));
309 /// ```
310 pub fn published(&self) -> Option<&FixedDateTime> {
311 self.published.as_ref()
312 }
313
314 /// Set the time that this entry was initially created or first made available.
315 ///
316 /// # Examples
317 ///
318 /// ```
319 /// use atom_syndication::Entry;
320 /// use atom_syndication::FixedDateTime;
321 /// use std::str::FromStr;
322 ///
323 /// let mut entry = Entry::default();
324 /// entry.set_published(FixedDateTime::from_str("2017-06-01T15:15:44-05:00").unwrap());
325 /// ```
326 pub fn set_published<V>(&mut self, published: V)
327 where
328 V: Into<Option<FixedDateTime>>,
329 {
330 self.published = published.into();
331 }
332
333 /// Return the information about the rights held in and over this entry.
334 ///
335 /// # Examples
336 ///
337 /// ```
338 /// use atom_syndication::{Entry, Text};
339 ///
340 /// let mut entry = Entry::default();
341 /// entry.set_rights(Text::from("© 2017 John Doe"));
342 /// assert_eq!(entry.rights().map(Text::as_str), Some("© 2017 John Doe"));
343 /// ```
344 pub fn rights(&self) -> Option<&Text> {
345 self.rights.as_ref()
346 }
347
348 /// Set the information about the rights held in and over this entry.
349 ///
350 /// # Examples
351 ///
352 /// ```
353 /// use atom_syndication::{Entry, Text};
354 ///
355 /// let mut entry = Entry::default();
356 /// entry.set_rights(Text::from("© 2017 John Doe"));
357 /// ```
358 pub fn set_rights<V>(&mut self, rights: V)
359 where
360 V: Into<Option<Text>>,
361 {
362 self.rights = rights.into();
363 }
364
365 /// Return the source of this entry if it was copied from another feed.
366 ///
367 /// # Examples
368 ///
369 /// ```
370 /// use atom_syndication::{Entry, Source};
371 ///
372 /// let mut entry = Entry::default();
373 /// entry.set_source(Source::default());
374 /// assert!(entry.source().is_some());
375 /// ```
376 pub fn source(&self) -> Option<&Source> {
377 self.source.as_ref()
378 }
379
380 /// Set the source of this entry if it was copied from another feed.
381 ///
382 /// # Examples
383 ///
384 /// ```
385 /// use atom_syndication::{Entry, Source};
386 ///
387 /// let mut entry = Entry::default();
388 /// entry.set_source(Source::default());
389 /// ```
390 pub fn set_source<V>(&mut self, source: V)
391 where
392 V: Into<Option<Source>>,
393 {
394 self.source = source.into()
395 }
396
397 /// Return the summary of this entry.
398 ///
399 /// # Examples
400 ///
401 /// ```
402 /// use atom_syndication::{Entry, Text};
403 ///
404 /// let mut entry = Entry::default();
405 /// entry.set_summary(Text::from("Entry summary."));
406 /// assert_eq!(entry.summary().map(Text::as_str), Some("Entry summary."));
407 /// ```
408 pub fn summary(&self) -> Option<&Text> {
409 self.summary.as_ref()
410 }
411
412 /// Set the summary of this entry.
413 ///
414 /// # Examples
415 ///
416 /// ```
417 /// use atom_syndication::{Entry, Text};
418 ///
419 /// let mut entry = Entry::default();
420 /// entry.set_summary(Text::from("Entry summary."));
421 /// ```
422 pub fn set_summary<V>(&mut self, summary: V)
423 where
424 V: Into<Option<Text>>,
425 {
426 self.summary = summary.into();
427 }
428
429 /// Return the content of this entry.
430 ///
431 /// # Examples
432 ///
433 /// ```
434 /// use atom_syndication::{Entry, Content};
435 ///
436 /// let mut entry = Entry::default();
437 /// entry.set_content(Content::default());
438 /// assert!(entry.content().is_some());
439 /// ```
440 pub fn content(&self) -> Option<&Content> {
441 self.content.as_ref()
442 }
443
444 /// Set the content of this entry.
445 ///
446 /// # Examples
447 ///
448 /// ```
449 /// use atom_syndication::{Entry, Content};
450 ///
451 /// let mut entry = Entry::default();
452 /// entry.set_content(Content::default());
453 /// assert!(entry.content().is_some());
454 /// ```
455 pub fn set_content<V>(&mut self, content: V)
456 where
457 V: Into<Option<Content>>,
458 {
459 self.content = content.into();
460 }
461
462 /// Return the extensions for this entry.
463 ///
464 /// # Examples
465 ///
466 /// ```
467 /// use std::collections::BTreeMap;
468 /// use atom_syndication::Entry;
469 /// use atom_syndication::extension::{ExtensionMap, Extension};
470 ///
471 /// let extension = Extension::default();
472 ///
473 /// let mut item_map = BTreeMap::<String, Vec<Extension>>::new();
474 /// item_map.insert("ext:name".to_string(), vec![extension]);
475 ///
476 /// let mut extension_map = ExtensionMap::default();
477 /// extension_map.insert("ext".to_string(), item_map);
478 ///
479 /// let mut entry = Entry::default();
480 /// entry.set_extensions(extension_map);
481 /// assert_eq!(entry.extensions()
482 /// .get("ext")
483 /// .and_then(|m| m.get("ext:name"))
484 /// .map(|v| v.len()),
485 /// Some(1));
486 /// ```
487 pub fn extensions(&self) -> &ExtensionMap {
488 &self.extensions
489 }
490
491 /// Set the extensions for this entry.
492 ///
493 /// # Examples
494 ///
495 /// ```
496 /// use atom_syndication::Entry;
497 /// use atom_syndication::extension::ExtensionMap;
498 ///
499 /// let mut entry = Entry::default();
500 /// entry.set_extensions(ExtensionMap::default());
501 /// ```
502 pub fn set_extensions<V>(&mut self, extensions: V)
503 where
504 V: Into<ExtensionMap>,
505 {
506 self.extensions = extensions.into()
507 }
508}
509
510impl FromXml for Entry {
511 fn from_xml<B: BufRead>(reader: &mut Reader<B>, _: Attributes<'_>) -> Result<Self, Error> {
512 let mut entry = Entry::default();
513 let mut buf = Vec::new();
514
515 loop {
516 match reader.read_event_into(&mut buf).map_err(XmlError::new)? {
517 Event::Start(element) => match decode(element.name().as_ref(), reader)? {
518 Cow::Borrowed("id") => entry.id = atom_text(reader)?.unwrap_or_default(),
519 Cow::Borrowed("title") => {
520 entry.title = Text::from_xml(reader, element.attributes())?
521 }
522 Cow::Borrowed("updated") => {
523 entry.updated =
524 atom_datetime(reader)?.unwrap_or_else(default_fixed_datetime)
525 }
526 Cow::Borrowed("author") => entry
527 .authors
528 .push(Person::from_xml(reader, element.attributes())?),
529 Cow::Borrowed("category") => {
530 entry.categories.push(Category::from_xml(reader, &element)?);
531 skip(element.name(), reader)?;
532 }
533 Cow::Borrowed("contributor") => entry
534 .contributors
535 .push(Person::from_xml(reader, element.attributes())?),
536 Cow::Borrowed("link") => {
537 entry.links.push(Link::from_xml(reader, &element)?);
538 skip(element.name(), reader)?;
539 }
540 Cow::Borrowed("published") => entry.published = atom_datetime(reader)?,
541 Cow::Borrowed("rights") => {
542 entry.rights = Some(Text::from_xml(reader, element.attributes())?)
543 }
544 Cow::Borrowed("source") => {
545 entry.source = Some(Source::from_xml(reader, element.attributes())?)
546 }
547 Cow::Borrowed("summary") => {
548 entry.summary = Some(Text::from_xml(reader, element.attributes())?)
549 }
550 Cow::Borrowed("content") => {
551 entry.content = Some(Content::from_xml(reader, element.attributes())?)
552 }
553 n => {
554 if let Some((ns, name)) = extension_name(n.as_ref()) {
555 parse_extension(
556 reader,
557 element.attributes(),
558 ns,
559 name,
560 &mut entry.extensions,
561 )?;
562 } else {
563 skip(element.name(), reader)?;
564 }
565 }
566 },
567 Event::End(_) => break,
568 Event::Eof => return Err(Error::Eof),
569 _ => {}
570 }
571
572 buf.clear();
573 }
574
575 Ok(entry)
576 }
577}
578
579impl ToXml for Entry {
580 fn to_xml<W: Write>(&self, writer: &mut Writer<W>) -> Result<(), XmlError> {
581 let name = "entry";
582 writer
583 .write_event(Event::Start(BytesStart::new(name)))
584 .map_err(XmlError::new)?;
585 writer.write_object_named(&self.title, "title")?;
586 writer.write_text_element("id", &self.id)?;
587 writer.write_text_element("updated", &self.updated.to_rfc3339())?;
588 writer.write_objects_named(&self.authors, "author")?;
589 writer.write_objects(&self.categories)?;
590 writer.write_objects_named(&self.contributors, "contributor")?;
591 writer.write_objects(&self.links)?;
592
593 if let Some(ref published) = self.published {
594 writer.write_text_element("published", &published.to_rfc3339())?;
595 }
596
597 if let Some(ref rights) = self.rights {
598 writer.write_object_named(rights, "rights")?;
599 }
600
601 if let Some(ref source) = self.source {
602 writer.write_object(source)?;
603 }
604
605 if let Some(ref summary) = self.summary {
606 writer.write_object_named(summary, "summary")?;
607 }
608
609 if let Some(ref content) = self.content {
610 writer.write_object(content)?;
611 }
612
613 for map in self.extensions.values() {
614 for extensions in map.values() {
615 writer.write_objects(extensions)?;
616 }
617 }
618
619 writer
620 .write_event(Event::End(BytesEnd::new(name)))
621 .map_err(XmlError::new)?;
622
623 Ok(())
624 }
625}
626
627impl Default for Entry {
628 fn default() -> Self {
629 Entry {
630 title: Text::default(),
631 id: String::new(),
632 updated: default_fixed_datetime(),
633 authors: Vec::new(),
634 categories: Vec::new(),
635 contributors: Vec::new(),
636 links: Vec::new(),
637 published: None,
638 rights: None,
639 source: None,
640 summary: None,
641 content: None,
642 extensions: ExtensionMap::default(),
643 }
644 }
645}
646
647#[cfg(feature = "builders")]
648impl EntryBuilder {
649 /// Builds a new `Entry`.
650 pub fn build(&self) -> Entry {
651 self.build_impl().unwrap()
652 }
653}