feedparser_rs/types/
entry.rs

1use super::{
2    common::{
3        Content, Enclosure, Link, MediaContent, MediaThumbnail, Person, Source, Tag, TextConstruct,
4    },
5    podcast::{ItunesEntryMeta, PodcastPerson, PodcastTranscript},
6};
7use chrono::{DateTime, Utc};
8
9/// Feed entry/item
10#[derive(Debug, Clone, Default)]
11pub struct Entry {
12    /// Unique entry identifier
13    pub id: Option<String>,
14    /// Entry title
15    pub title: Option<String>,
16    /// Detailed title with metadata
17    pub title_detail: Option<TextConstruct>,
18    /// Primary link
19    pub link: Option<String>,
20    /// All links associated with this entry
21    pub links: Vec<Link>,
22    /// Short description/summary
23    pub summary: Option<String>,
24    /// Detailed summary with metadata
25    pub summary_detail: Option<TextConstruct>,
26    /// Full content blocks
27    pub content: Vec<Content>,
28    /// Publication date
29    pub published: Option<DateTime<Utc>>,
30    /// Last update date
31    pub updated: Option<DateTime<Utc>>,
32    /// Creation date
33    pub created: Option<DateTime<Utc>>,
34    /// Expiration date
35    pub expired: Option<DateTime<Utc>>,
36    /// Primary author name
37    pub author: Option<String>,
38    /// Detailed author information
39    pub author_detail: Option<Person>,
40    /// All authors
41    pub authors: Vec<Person>,
42    /// Contributors
43    pub contributors: Vec<Person>,
44    /// Publisher name
45    pub publisher: Option<String>,
46    /// Detailed publisher information
47    pub publisher_detail: Option<Person>,
48    /// Tags/categories
49    pub tags: Vec<Tag>,
50    /// Media enclosures (audio, video, etc.)
51    pub enclosures: Vec<Enclosure>,
52    /// Comments URL or text
53    pub comments: Option<String>,
54    /// Source feed reference
55    pub source: Option<Source>,
56    /// iTunes episode metadata (if present)
57    pub itunes: Option<ItunesEntryMeta>,
58    /// Dublin Core creator (author fallback)
59    pub dc_creator: Option<String>,
60    /// Dublin Core date (publication date fallback)
61    pub dc_date: Option<DateTime<Utc>>,
62    /// Dublin Core subjects (tags)
63    pub dc_subject: Vec<String>,
64    /// Dublin Core rights (copyright)
65    pub dc_rights: Option<String>,
66    /// Media RSS thumbnails
67    pub media_thumbnails: Vec<MediaThumbnail>,
68    /// Media RSS content items
69    pub media_content: Vec<MediaContent>,
70    /// Podcast 2.0 transcripts for this episode
71    pub podcast_transcripts: Vec<PodcastTranscript>,
72    /// Podcast 2.0 persons for this episode (hosts, guests, etc.)
73    pub podcast_persons: Vec<PodcastPerson>,
74}
75
76impl Entry {
77    /// Creates `Entry` with pre-allocated capacity for collections
78    ///
79    /// Pre-allocates space for typical entry fields:
80    /// - 1-2 links (alternate, related)
81    /// - 1 content block
82    /// - 1 author
83    /// - 2-3 tags
84    /// - 0-1 enclosures
85    /// - 2 podcast transcripts (typical for podcasts with multiple languages)
86    /// - 4 podcast persons (host, co-hosts, guests)
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// use feedparser_rs::Entry;
92    ///
93    /// let entry = Entry::with_capacity();
94    /// ```
95    #[must_use]
96    pub fn with_capacity() -> Self {
97        Self {
98            links: Vec::with_capacity(2),
99            content: Vec::with_capacity(1),
100            authors: Vec::with_capacity(1),
101            contributors: Vec::with_capacity(0),
102            tags: Vec::with_capacity(3),
103            enclosures: Vec::with_capacity(1),
104            dc_subject: Vec::with_capacity(2),
105            media_thumbnails: Vec::with_capacity(1),
106            media_content: Vec::with_capacity(1),
107            podcast_transcripts: Vec::with_capacity(2),
108            podcast_persons: Vec::with_capacity(4),
109            ..Default::default()
110        }
111    }
112
113    /// Sets title field with `TextConstruct`, storing both simple and detailed versions
114    ///
115    /// # Examples
116    ///
117    /// ```
118    /// use feedparser_rs::{Entry, TextConstruct};
119    ///
120    /// let mut entry = Entry::default();
121    /// entry.set_title(TextConstruct::text("Great Article"));
122    /// assert_eq!(entry.title.as_deref(), Some("Great Article"));
123    /// ```
124    #[inline]
125    pub fn set_title(&mut self, mut text: TextConstruct) {
126        self.title = Some(std::mem::take(&mut text.value));
127        self.title_detail = Some(text);
128    }
129
130    /// Sets summary field with `TextConstruct`, storing both simple and detailed versions
131    ///
132    /// # Examples
133    ///
134    /// ```
135    /// use feedparser_rs::{Entry, TextConstruct};
136    ///
137    /// let mut entry = Entry::default();
138    /// entry.set_summary(TextConstruct::text("A summary"));
139    /// assert_eq!(entry.summary.as_deref(), Some("A summary"));
140    /// ```
141    #[inline]
142    pub fn set_summary(&mut self, mut text: TextConstruct) {
143        self.summary = Some(std::mem::take(&mut text.value));
144        self.summary_detail = Some(text);
145    }
146
147    /// Sets author field with `Person`, storing both simple and detailed versions
148    ///
149    /// # Examples
150    ///
151    /// ```
152    /// use feedparser_rs::{Entry, Person};
153    ///
154    /// let mut entry = Entry::default();
155    /// entry.set_author(Person::from_name("Jane Doe"));
156    /// assert_eq!(entry.author.as_deref(), Some("Jane Doe"));
157    /// ```
158    #[inline]
159    pub fn set_author(&mut self, mut person: Person) {
160        self.author = person.name.take();
161        self.author_detail = Some(person);
162    }
163
164    /// Sets publisher field with `Person`, storing both simple and detailed versions
165    ///
166    /// # Examples
167    ///
168    /// ```
169    /// use feedparser_rs::{Entry, Person};
170    ///
171    /// let mut entry = Entry::default();
172    /// entry.set_publisher(Person::from_name("ACME Corp"));
173    /// assert_eq!(entry.publisher.as_deref(), Some("ACME Corp"));
174    /// ```
175    #[inline]
176    pub fn set_publisher(&mut self, mut person: Person) {
177        self.publisher = person.name.take();
178        self.publisher_detail = Some(person);
179    }
180}
181
182#[cfg(test)]
183mod tests {
184    use super::*;
185
186    #[test]
187    fn test_entry_default() {
188        let entry = Entry::default();
189        assert!(entry.id.is_none());
190        assert!(entry.title.is_none());
191        assert!(entry.links.is_empty());
192        assert!(entry.content.is_empty());
193        assert!(entry.authors.is_empty());
194    }
195
196    #[test]
197    #[allow(clippy::redundant_clone)]
198    fn test_entry_clone() {
199        fn create_entry() -> Entry {
200            Entry {
201                title: Some("Test".to_string()),
202                links: vec![Link::default()],
203                ..Default::default()
204            }
205        }
206        let entry = create_entry();
207        let cloned = entry.clone();
208        assert_eq!(cloned.title.as_deref(), Some("Test"));
209        assert_eq!(cloned.links.len(), 1);
210    }
211}