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}