1use serde::{Deserialize, Deserializer, Serialize};
2use synd_feed::types::{Category, FeedType, FeedUrl, Requirement, Time};
3
4use super::PageInfo;
5
6#[derive(Debug, Clone, Deserialize)]
7pub struct SubscriptionPayload {
8 pub feeds: FeedConnection,
9}
10
11#[derive(Debug, Clone, Deserialize)]
12#[serde(rename_all = "camelCase")]
13pub struct FeedConnection {
14 pub nodes: Vec<SubscribedFeed>,
15 pub page_info: PageInfo,
16}
17
18#[derive(Debug, Clone, Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct SubscribedFeed {
21 pub url: FeedUrl,
22 #[serde(default, with = "super::requirement")]
23 pub requirement: Option<Requirement>,
24 pub category: Option<Category<'static>>,
25 pub crawl_policy: CrawlPolicy,
26 pub feed: Option<FeedDetails>,
27}
28
29#[derive(Debug, Clone, Deserialize)]
30#[serde(rename_all = "camelCase")]
31#[cfg_attr(any(test, feature = "fake"), derive(fake::Dummy))]
32pub struct CrawlPolicy {
33 pub polling: PollingPolicy,
34}
35
36#[derive(Debug, Clone)]
37#[cfg_attr(any(test, feature = "fake"), derive(fake::Dummy))]
38pub enum PollingPolicy {
39 Manual,
40 Interval {
41 seconds: PollingIntervalSeconds,
42 },
43 Other {
44 kind: String,
45 interval_seconds: Option<i64>,
46 },
47}
48
49impl<'de> Deserialize<'de> for PollingPolicy {
50 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
51 where
52 D: Deserializer<'de>,
53 {
54 let wire = PollingPolicyWire::deserialize(deserializer)?;
55 match (wire.kind.as_str(), wire.interval_seconds) {
56 ("MANUAL", None) => Ok(Self::Manual),
57 ("MANUAL", Some(_)) => Err(serde::de::Error::custom(
58 "MANUAL polling policy must omit intervalSeconds",
59 )),
60 ("INTERVAL", Some(seconds)) => Ok(Self::Interval {
61 seconds: seconds.try_into().map_err(serde::de::Error::custom)?,
62 }),
63 ("INTERVAL", None) => Err(serde::de::Error::custom(
64 "INTERVAL polling policy requires intervalSeconds",
65 )),
66 (_, interval_seconds) => Ok(Self::Other {
67 kind: wire.kind,
68 interval_seconds,
69 }),
70 }
71 }
72}
73
74#[derive(Deserialize)]
75#[serde(rename_all = "camelCase")]
76struct PollingPolicyWire {
77 kind: String,
78 interval_seconds: Option<i64>,
79}
80
81#[derive(Debug, Clone, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct FeedDetails {
84 #[serde(rename = "type")]
85 pub feed_type: GraphqlFeedType,
86 pub title: Option<String>,
87 pub updated: Option<Time>,
88 pub website_url: Option<String>,
89 pub description: Option<String>,
90 pub generator: Option<String>,
91 pub entries: EntryMetaConnection,
92 pub links: LinkConnection,
93 pub authors: AuthorsConnection,
94}
95
96#[derive(Debug, Clone, PartialEq, Eq)]
97pub enum GraphqlFeedType {
98 Atom,
99 Json,
100 Rss0,
101 Rss1,
102 Rss2,
103 Other(String),
104}
105
106#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
107#[error("unsupported GraphQL feed type: {0}")]
108pub struct UnsupportedFeedType(pub String);
109
110impl TryFrom<GraphqlFeedType> for FeedType {
111 type Error = UnsupportedFeedType;
112
113 fn try_from(value: GraphqlFeedType) -> Result<Self, Self::Error> {
114 match value {
115 GraphqlFeedType::Atom => Ok(Self::Atom),
116 GraphqlFeedType::Json => Ok(Self::JSON),
117 GraphqlFeedType::Rss0 => Ok(Self::RSS0),
118 GraphqlFeedType::Rss1 => Ok(Self::RSS1),
119 GraphqlFeedType::Rss2 => Ok(Self::RSS2),
120 GraphqlFeedType::Other(value) => Err(UnsupportedFeedType(value)),
121 }
122 }
123}
124
125impl<'de> Deserialize<'de> for GraphqlFeedType {
126 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
127 where
128 D: Deserializer<'de>,
129 {
130 let value = String::deserialize(deserializer)?;
131 Ok(match value.as_str() {
132 "ATOM" => Self::Atom,
133 "JSON" => Self::Json,
134 "RSS0" => Self::Rss0,
135 "RSS1" => Self::Rss1,
136 "RSS2" => Self::Rss2,
137 _ => Self::Other(value),
138 })
139 }
140}
141
142#[derive(Debug, Clone, Deserialize)]
143#[cfg_attr(any(test, feature = "fake"), derive(fake::Dummy))]
144pub struct EntryMetaConnection {
145 pub nodes: Vec<EntryMeta>,
146}
147
148#[derive(Debug, Clone, Deserialize)]
149#[cfg_attr(any(test, feature = "fake"), derive(fake::Dummy))]
150pub struct LinkConnection {
151 pub nodes: Vec<Link>,
152}
153
154#[derive(Debug, Clone, Deserialize)]
155pub struct AuthorsConnection {
156 pub nodes: Vec<String>,
157}
158
159#[derive(Debug, Clone, Deserialize)]
160#[serde(rename_all = "camelCase")]
161#[cfg_attr(any(test, feature = "fake"), derive(fake::Dummy))]
162pub struct Link {
163 pub href: String,
164 pub rel: Option<String>,
165 pub media_type: Option<String>,
166 pub title: Option<String>,
167}
168
169#[derive(Debug, Clone, Deserialize)]
170#[cfg_attr(any(test, feature = "fake"), derive(fake::Dummy))]
171pub struct EntryMeta {
172 pub title: Option<String>,
173 pub published: Option<Time>,
174 pub updated: Option<Time>,
175 pub summary: Option<String>,
176}
177
178#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
179#[serde(rename_all = "camelCase")]
180pub struct SubscribeFeedInput {
181 pub url: FeedUrl,
182 #[serde(default, with = "super::requirement")]
183 pub requirement: Option<Requirement>,
184 pub category: Option<Category<'static>>,
185 pub crawl_policy: Option<CrawlPolicyInput>,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
189#[serde(rename_all = "camelCase")]
190pub struct CrawlPolicyInput {
191 pub polling: PollingPolicyInput,
192}
193
194#[derive(Debug, Clone, PartialEq, Eq)]
195pub enum PollingPolicyInput {
196 Manual,
197 Interval { seconds: PollingIntervalSeconds },
198}
199
200#[derive(Debug, Clone, Copy, PartialEq, Eq)]
201#[cfg_attr(any(test, feature = "fake"), derive(fake::Dummy))]
202pub struct PollingIntervalSeconds(i64);
203
204impl PollingIntervalSeconds {
205 pub fn get(self) -> i64 {
206 self.0
207 }
208}
209
210#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
211#[error("polling interval must be greater than zero")]
212pub struct InvalidPollingInterval;
213
214impl TryFrom<i64> for PollingIntervalSeconds {
215 type Error = InvalidPollingInterval;
216
217 fn try_from(seconds: i64) -> Result<Self, Self::Error> {
218 if seconds > 0 {
219 Ok(Self(seconds))
220 } else {
221 Err(InvalidPollingInterval)
222 }
223 }
224}
225
226impl Serialize for PollingPolicyInput {
227 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
228 where
229 S: serde::Serializer,
230 {
231 let wire = match self {
232 Self::Manual => PollingPolicyInputWire {
233 kind: PollingPolicyInputKind::Manual,
234 interval_seconds: None,
235 },
236 Self::Interval { seconds } => PollingPolicyInputWire {
237 kind: PollingPolicyInputKind::Interval,
238 interval_seconds: Some(seconds.get()),
239 },
240 };
241 wire.serialize(serializer)
242 }
243}
244
245#[derive(Serialize)]
246#[serde(rename_all = "camelCase")]
247struct PollingPolicyInputWire {
248 kind: PollingPolicyInputKind,
249 interval_seconds: Option<i64>,
250}
251
252#[derive(Serialize)]
253#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
254enum PollingPolicyInputKind {
255 Manual,
256 Interval,
257}
258
259#[derive(Debug, Clone, Deserialize)]
260#[serde(rename_all = "camelCase")]
261pub struct SubscribeFeedPayload {
262 pub status: ResponseStatus,
263 pub url: FeedUrl,
264 pub disposition: SubscribeDisposition,
265}
266
267#[derive(Debug, Clone, PartialEq, Eq)]
268pub enum SubscribeDisposition {
269 Subscribed,
270 Changed,
271 Other(String),
272}
273
274impl<'de> Deserialize<'de> for SubscribeDisposition {
275 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
276 where
277 D: Deserializer<'de>,
278 {
279 let value = String::deserialize(deserializer)?;
280 Ok(match value.as_str() {
281 "SUBSCRIBED" => Self::Subscribed,
282 "CHANGED" => Self::Changed,
283 _ => Self::Other(value),
284 })
285 }
286}
287
288#[derive(Debug, Clone, Deserialize)]
289#[serde(rename_all = "camelCase")]
290pub struct UnsubscribeFeedPayload {
291 pub status: ResponseStatus,
292 pub url: FeedUrl,
293 pub disposition: UnsubscribeDisposition,
294}
295
296#[derive(Debug, Clone, PartialEq, Eq)]
297pub enum UnsubscribeDisposition {
298 Unsubscribed,
299 Other(String),
300}
301
302impl<'de> Deserialize<'de> for UnsubscribeDisposition {
303 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
304 where
305 D: Deserializer<'de>,
306 {
307 let value = String::deserialize(deserializer)?;
308 Ok(match value.as_str() {
309 "UNSUBSCRIBED" => Self::Unsubscribed,
310 _ => Self::Other(value),
311 })
312 }
313}
314
315#[derive(Debug, Clone, Deserialize)]
316pub struct ResponseStatus {
317 pub code: ResponseCode,
318}
319
320#[derive(Debug, Clone, PartialEq, Eq)]
321pub enum ResponseCode {
322 Ok,
323 Unauthorized,
324 InvalidFeedUrl,
325 FeedUnavailable,
326 InternalError,
327 Other(String),
328}
329
330impl<'de> Deserialize<'de> for ResponseCode {
331 fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
332 where
333 D: Deserializer<'de>,
334 {
335 let value = String::deserialize(deserializer)?;
336 Ok(match value.as_str() {
337 "OK" => Self::Ok,
338 "UNAUTHORIZED" => Self::Unauthorized,
339 "INVALID_FEED_URL" => Self::InvalidFeedUrl,
340 "FEED_UNAVAILABLE" => Self::FeedUnavailable,
341 "INTERNAL_ERROR" => Self::InternalError,
342 _ => Self::Other(value),
343 })
344 }
345}