Skip to main content

rama_http/protocols/rss/atom/
builder.rs

1use std::marker::PhantomData;
2
3use jiff::Timestamp;
4use rama_net::uri::Uri;
5use rama_utils::macros::generate_set_and_with;
6
7use super::types::{
8    AtomCategory, AtomEntry, AtomFeed, AtomGenerator, AtomLink, AtomPerson, AtomText,
9};
10use crate::protocols::rss::feed_ext::FeedExtensions;
11use crate::protocols::rss::rss2::{Missing, Present};
12
13/// Type-state builder for [`AtomFeed`].
14///
15/// `I`, `T`, and `U` track whether `id`, `title`, and `updated` have been
16/// provided. [`build`](AtomFeedBuilder::build) is only available once all
17/// three are `Present`.
18pub struct AtomFeedBuilder<I, T, U> {
19    pub(super) id: Option<Uri>,
20    pub(super) title: AtomText,
21    pub(super) updated: Option<Timestamp>,
22    pub(super) authors: Vec<AtomPerson>,
23    pub(super) links: Vec<AtomLink>,
24    pub(super) categories: Vec<AtomCategory>,
25    pub(super) contributors: Vec<AtomPerson>,
26    pub(super) generator: Option<AtomGenerator>,
27    pub(super) icon: Option<Uri>,
28    pub(super) logo: Option<Uri>,
29    pub(super) rights: Option<AtomText>,
30    pub(super) subtitle: Option<AtomText>,
31    pub(super) entries: Vec<AtomEntry>,
32    pub(super) extensions: FeedExtensions,
33    pub(super) _pd: PhantomData<(I, T, U)>,
34}
35
36impl AtomFeedBuilder<Missing, Missing, Missing> {
37    pub(super) fn new() -> Self {
38        Self {
39            id: None,
40            title: AtomText::text(""),
41            updated: None,
42            authors: Vec::new(),
43            links: Vec::new(),
44            categories: Vec::new(),
45            contributors: Vec::new(),
46            generator: None,
47            icon: None,
48            logo: None,
49            rights: None,
50            subtitle: None,
51            entries: Vec::new(),
52            extensions: FeedExtensions::default(),
53            _pd: PhantomData,
54        }
55    }
56}
57
58impl<T, U> AtomFeedBuilder<Missing, T, U> {
59    #[must_use]
60    pub fn id(self, id: Uri) -> AtomFeedBuilder<Present, T, U> {
61        AtomFeedBuilder {
62            id: Some(id),
63            title: self.title,
64            updated: self.updated,
65            authors: self.authors,
66            links: self.links,
67            categories: self.categories,
68            contributors: self.contributors,
69            generator: self.generator,
70            icon: self.icon,
71            logo: self.logo,
72            rights: self.rights,
73            subtitle: self.subtitle,
74            entries: self.entries,
75            extensions: self.extensions,
76            _pd: PhantomData,
77        }
78    }
79}
80
81impl<I, U> AtomFeedBuilder<I, Missing, U> {
82    #[must_use]
83    pub fn title(self, title: impl Into<AtomText>) -> AtomFeedBuilder<I, Present, U> {
84        AtomFeedBuilder {
85            id: self.id,
86            title: title.into(),
87            updated: self.updated,
88            authors: self.authors,
89            links: self.links,
90            categories: self.categories,
91            contributors: self.contributors,
92            generator: self.generator,
93            icon: self.icon,
94            logo: self.logo,
95            rights: self.rights,
96            subtitle: self.subtitle,
97            entries: self.entries,
98            extensions: self.extensions,
99            _pd: PhantomData,
100        }
101    }
102}
103
104impl<I, T> AtomFeedBuilder<I, T, Missing> {
105    #[must_use]
106    pub fn updated(self, ts: Timestamp) -> AtomFeedBuilder<I, T, Present> {
107        AtomFeedBuilder {
108            id: self.id,
109            title: self.title,
110            updated: Some(ts),
111            authors: self.authors,
112            links: self.links,
113            categories: self.categories,
114            contributors: self.contributors,
115            generator: self.generator,
116            icon: self.icon,
117            logo: self.logo,
118            rights: self.rights,
119            subtitle: self.subtitle,
120            entries: self.entries,
121            extensions: self.extensions,
122            _pd: PhantomData,
123        }
124    }
125}
126
127impl<I, T, U> AtomFeedBuilder<I, T, U> {
128    generate_set_and_with! {
129        /// Append a feed-level author. Call multiple times to add more.
130        pub fn author(mut self, author: AtomPerson) -> Self {
131            self.authors.push(author);
132            self
133        }
134    }
135
136    generate_set_and_with! {
137        /// Append a feed-level `<link>`. Call multiple times to add more.
138        pub fn link(mut self, link: AtomLink) -> Self {
139            self.links.push(link);
140            self
141        }
142    }
143
144    generate_set_and_with! {
145        /// Append a feed-level category. Call multiple times to add more.
146        pub fn category(mut self, cat: AtomCategory) -> Self {
147            self.categories.push(cat);
148            self
149        }
150    }
151
152    generate_set_and_with! {
153        /// Append a feed-level contributor. Call multiple times to add more.
154        pub fn contributor(mut self, c: AtomPerson) -> Self {
155            self.contributors.push(c);
156            self
157        }
158    }
159
160    generate_set_and_with! {
161        pub fn generator(mut self, generator: AtomGenerator) -> Self {
162            self.generator = Some(generator);
163            self
164        }
165    }
166
167    generate_set_and_with! {
168        pub fn icon(mut self, icon: Uri) -> Self {
169            self.icon = Some(icon);
170            self
171        }
172    }
173
174    generate_set_and_with! {
175        pub fn logo(mut self, logo: Uri) -> Self {
176            self.logo = Some(logo);
177            self
178        }
179    }
180
181    generate_set_and_with! {
182        pub fn rights(mut self, rights: impl Into<AtomText>) -> Self {
183            self.rights = Some(rights.into());
184            self
185        }
186    }
187
188    generate_set_and_with! {
189        pub fn subtitle(mut self, subtitle: impl Into<AtomText>) -> Self {
190            self.subtitle = Some(subtitle.into());
191            self
192        }
193    }
194
195    generate_set_and_with! {
196        /// Append a single entry. Call multiple times to attach more.
197        pub fn entry(mut self, entry: AtomEntry) -> Self {
198            self.entries.push(entry);
199            self
200        }
201    }
202
203    generate_set_and_with! {
204        pub fn entries(mut self, entries: impl IntoIterator<Item = AtomEntry>) -> Self {
205            self.entries.extend(entries);
206            self
207        }
208    }
209
210    generate_set_and_with! {
211        pub fn feed_extensions(mut self, ext: FeedExtensions) -> Self {
212            self.extensions = ext;
213            self
214        }
215    }
216}
217
218impl AtomFeedBuilder<Present, Present, Present> {
219    #[must_use]
220    pub fn build(self) -> AtomFeed {
221        AtomFeed {
222            #[expect(
223                clippy::expect_used,
224                reason = "type-state guarantees `id` is Present once build() is callable"
225            )]
226            id: self.id.expect("id is Present"),
227            title: self.title,
228            #[expect(
229                clippy::expect_used,
230                reason = "type-state guarantees `updated` is Present once build() is callable"
231            )]
232            updated: self.updated.expect("updated is Present"),
233            authors: self.authors,
234            links: self.links,
235            categories: self.categories,
236            contributors: self.contributors,
237            generator: self.generator,
238            icon: self.icon,
239            logo: self.logo,
240            rights: self.rights,
241            subtitle: self.subtitle,
242            entries: self.entries,
243            extensions: self.extensions,
244        }
245    }
246}