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}