Skip to main content

other_pocket/
send.rs

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}