1use crate::{add::PocketAddedItem, serialization::*};
2use serde::{Deserialize, Serialize};
3use url::Url;
4
5#[derive(Serialize)]
6pub struct PocketSendRequest<'a> {
7 pub actions: &'a [&'a PocketSendAction],
8}
9
10#[derive(Debug, PartialEq, Serialize)]
11#[serde(tag = "action", rename_all = "snake_case")]
12pub enum PocketSendAction {
13 Add {
14 #[serde(serialize_with = "optional_to_string")]
15 item_id: Option<u64>,
16 ref_id: Option<String>,
17 tags: Option<String>,
18 #[serde(serialize_with = "optional_to_string")]
19 time: Option<u64>,
20 title: Option<String>,
21 #[serde(with = "url_serde")]
22 url: Option<Url>,
23 },
24 Archive {
25 #[serde(serialize_with = "to_string")]
26 item_id: u64,
27 #[serde(serialize_with = "optional_to_string")]
28 time: Option<u64>,
29 },
30 Readd {
31 #[serde(serialize_with = "to_string")]
32 item_id: u64,
33 #[serde(serialize_with = "optional_to_string")]
34 time: Option<u64>,
35 },
36 Favorite {
37 #[serde(serialize_with = "to_string")]
38 item_id: u64,
39 #[serde(serialize_with = "optional_to_string")]
40 time: Option<u64>,
41 },
42 Unfavorite {
43 #[serde(serialize_with = "to_string")]
44 item_id: u64,
45 #[serde(serialize_with = "optional_to_string")]
46 time: Option<u64>,
47 },
48 Delete {
49 #[serde(serialize_with = "to_string")]
50 item_id: u64,
51 #[serde(serialize_with = "optional_to_string")]
52 time: Option<u64>,
53 },
54 TagsAdd {
55 #[serde(serialize_with = "to_string")]
56 item_id: u64,
57 tags: String,
58 #[serde(serialize_with = "optional_to_string")]
59 time: Option<u64>,
60 },
61 TagsRemove {
62 #[serde(serialize_with = "to_string")]
63 item_id: u64,
64 tags: String,
65 #[serde(serialize_with = "optional_to_string")]
66 time: Option<u64>,
67 },
68 TagsReplace {
69 #[serde(serialize_with = "to_string")]
70 item_id: u64,
71 tags: String,
72 #[serde(serialize_with = "optional_to_string")]
73 time: Option<u64>,
74 },
75 TagsClear {
76 #[serde(serialize_with = "to_string")]
77 item_id: u64,
78 #[serde(serialize_with = "optional_to_string")]
79 time: Option<u64>,
80 },
81 TagRename {
82 old_tag: String,
83 new_tag: String,
84 #[serde(serialize_with = "optional_to_string")]
85 time: Option<u64>,
86 },
87 TagDelete {
88 tag: String,
89 #[serde(serialize_with = "optional_to_string")]
90 time: Option<u64>,
91 },
92}
93
94#[derive(Deserialize, Debug, PartialEq)]
95pub struct PocketSendResponse {
96 pub status: u16,
97 pub action_results: Vec<SendActionResult>,
98 pub action_errors: Vec<Option<SendActionError>>,
99}
100
101#[derive(Deserialize, Debug, PartialEq)]
102#[serde(untagged)]
103pub enum SendActionResult {
104 #[serde(deserialize_with = "true_to_unit_variant")]
105 Success,
106 #[serde(deserialize_with = "false_to_unit_variant")]
107 Failure,
108 Add(Box<PocketAddedItem>),
109}
110
111#[derive(Deserialize, Debug, PartialEq)]
112pub struct SendActionError {
113 code: u16,
114 message: String,
115 #[serde(rename = "type")]
116 error_type: String,
117}
118
119#[cfg(test)]
120mod test {
121 use super::*;
122 use crate::PocketItemHas;
123 use chrono::{TimeZone, Utc};
124
125 #[test]
126 fn test_deserialize_send_response() {
127 let expected = PocketSendResponse {
128 status: 1,
129 action_results: vec![SendActionResult::Success, SendActionResult::Failure],
130 action_errors: vec![
131 None,
132 Some(SendActionError {
133 code: 422,
134 message: "Invalid/non-existent URL".to_string(),
135 error_type: "Unprocessable Entity".to_string(),
136 }),
137 ],
138 };
139 let response = r#"
140 {
141 "action_results":[
142 true,
143 false
144 ],
145 "action_errors":[
146 null,
147 {
148 "code": 422,
149 "message": "Invalid/non-existent URL",
150 "type": "Unprocessable Entity"
151 }
152 ],
153 "status":1
154 }
155 "#;
156
157 let actual: PocketSendResponse = serde_json::from_str(&response).unwrap();
158
159 assert_eq!(actual, expected);
160 }
161
162 #[test]
163 fn test_deserialize_send_response_add() {
164 let expected = PocketSendResponse {
165 status: 1,
166 action_results: vec![
167 SendActionResult::Add(
168 Box::new(PocketAddedItem {
169 item_id: 1502819,
170 normal_url: Url::parse("http://example.com").unwrap(),
171 resolved_id: 1502819,
172 extended_item_id: 1502819,
173 resolved_url: Url::parse("https://example.com").ok(),
174 domain_id: 85964,
175 origin_domain_id: 772,
176 response_code: 200,
177 mime_type: "text/html".parse().ok(),
178 content_length: 648,
179 encoding: "utf-8".to_string(),
180 date_resolved: Utc.datetime_from_str("2020-08-04 22:41:28", FORMAT).ok(),
181 date_published: None,
182 title: "Example Domain".to_string(),
183 excerpt: "This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission. More information...".to_string(),
184 word_count: 28,
185 innerdomain_redirect: true,
186 login_required: false,
187 has_image: PocketItemHas::No,
188 has_video: PocketItemHas::No,
189 is_index: true,
190 is_article: false,
191 used_fallback: true,
192 lang: Some("".to_string()),
193 time_first_parsed: None,
194 authors: Some(vec![]),
195 images: Some(vec![]),
196 videos: Some(vec![]),
197 resolved_normal_url: Url::parse("http://example.com").ok(),
198 given_url: Url::parse("https://example.com/").unwrap(),
199 })
200 ),
201 ],
202 action_errors: vec![None],
203 };
204 let response = r#"
205 {
206 "action_results":[
207 {
208 "item_id":"1502819",
209 "normal_url":"http://example.com",
210 "resolved_id":"1502819",
211 "extended_item_id":"1502819",
212 "resolved_url":"https:example.com/",
213 "domain_id":"85964",
214 "origin_domain_id":"772",
215 "response_code":"200",
216 "mime_type":"text/html",
217 "content_length":"648",
218 "encoding":"utf-8",
219 "date_resolved":"2020-08-04 22:41:28",
220 "date_published":"0000-00-00 00:00:00",
221 "title":"Example Domain",
222 "excerpt":"This domain is for use in illustrative examples in documents. You may use this domain in literature without prior coordination or asking for permission. More information...",
223 "word_count":"28",
224 "innerdomain_redirect":"1",
225 "login_required":"0",
226 "has_image":"0",
227 "has_video":"0",
228 "is_index":"1",
229 "is_article":"0",
230 "used_fallback":"1",
231 "lang":"",
232 "time_first_parsed":"0",
233 "authors":[
234
235 ],
236 "images":[
237
238 ],
239 "videos":[
240
241 ],
242 "resolved_normal_url":"http://example.com",
243 "given_url":"https://example.com/"
244 }
245 ],
246 "action_errors":[
247 null
248 ],
249 "status":1
250 }
251 "#;
252
253 let actual: PocketSendResponse = serde_json::from_str(&response).unwrap();
254
255 assert_eq!(actual, expected);
256 }
257}