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