rama_http/protocols/rss/feed_ext/mod.rs
1//! RSS/Atom feed extension system.
2//!
3//! Six supported extension namespaces: iTunes, Podcasting 2.0, Dublin Core,
4//! `content:encoded`, Media RSS, and Podlove Simple Chapters
5//! ([`PodloveChapters`], item-level only). Each contributes a typed struct
6//! stored on [`ItemExtensions`] (item-level) and [`FeedExtensions`]
7//! (feed/channel-level) — direct field access (`item.extensions.itunes`)
8//! or via the inherent shortcuts on the per-format item/feed types
9//! (`.itunes()`, `.podcast()`, `.dublin_core()`, `.content()`, `.media()`,
10//! `.podlove()`).
11
12// Per-extension type definitions, organised by namespace.
13pub mod content;
14pub mod dublin_core;
15pub mod itunes;
16pub mod media;
17pub mod podcast;
18pub mod podlove;
19
20// Shared parser / writer / element + attribute names. Internal infrastructure
21// the per-format readers and writers (in [`super::rss2`] and [`super::atom`])
22// call into.
23pub(super) mod names;
24pub(super) mod parse;
25pub(super) mod write;
26
27pub use content::Content;
28pub use dublin_core::{DublinCore, DublinCoreFeed};
29pub use itunes::{ITunes, ITunesFeed};
30pub use media::{MediaContent, MediaRss, MediaThumbnail};
31pub use podcast::{
32 Podcast, PodcastAlternateEnclosure, PodcastChapters, PodcastEpisode, PodcastFeed,
33 PodcastFunding, PodcastIntegrity, PodcastLocation, PodcastPerson, PodcastRemoteItem,
34 PodcastSeason, PodcastSoundbite, PodcastSource, PodcastTrailer, PodcastTranscript,
35};
36pub use podlove::{PodloveChapter, PodloveChapters};
37
38// ---------------------------------------------------------------------------
39// Extension containers
40// ---------------------------------------------------------------------------
41
42/// Extension container for feed items (RSS 2.0 items and Atom entries).
43///
44/// Each present extension is boxed so the empty case is just six
45/// pointer-sized `None`s (48 B on a 64-bit target), not six inline
46/// extension structs (≥800 B). Most items have at most one or two
47/// extensions populated; the boxed-Option shape pays heap only for what's
48/// actually set and lets `Box<T>` auto-deref carry the field-access API.
49#[derive(Debug, Clone, Default, PartialEq)]
50pub struct ItemExtensions {
51 pub itunes: Option<Box<ITunes>>,
52 pub podcast: Option<Box<Podcast>>,
53 pub dublin_core: Option<Box<DublinCore>>,
54 pub content: Option<Box<Content>>,
55 pub media: Option<Box<MediaRss>>,
56 pub podlove: Option<Box<PodloveChapters>>,
57}
58
59impl ItemExtensions {
60 #[must_use]
61 pub fn is_empty(&self) -> bool {
62 self.itunes.is_none()
63 && self.podcast.is_none()
64 && self.dublin_core.is_none()
65 && self.content.is_none()
66 && self.media.is_none()
67 && self.podlove.is_none()
68 }
69}
70
71/// Extension container for feeds (channel-level for RSS 2.0, feed-level for Atom).
72///
73/// Same boxed-Option shape as [`ItemExtensions`]; see that type for the
74/// rationale.
75#[derive(Debug, Clone, Default, PartialEq)]
76pub struct FeedExtensions {
77 pub itunes: Option<Box<ITunesFeed>>,
78 pub podcast: Option<Box<PodcastFeed>>,
79 pub dublin_core: Option<Box<DublinCoreFeed>>,
80}
81
82impl FeedExtensions {
83 #[must_use]
84 pub fn is_empty(&self) -> bool {
85 self.itunes.is_none() && self.podcast.is_none() && self.dublin_core.is_none()
86 }
87}