flickr_api/
upload_photo.rs

1use crate::*;
2use reqwest::multipart::{Form, Part};
3use tokio::fs::read;
4
5#[derive(Debug, Serialize, Deserialize, PartialEq)]
6#[serde(rename = "rsp")]
7struct UploadXMLAnswer {
8    stat: String,
9
10    #[serde(flatten)]
11    content: UploadXMLPayload,
12}
13
14#[derive(Debug, Serialize, Deserialize, PartialEq)]
15enum UploadXMLPayload {
16    #[serde(rename = "photoid")]
17    PhotoId {
18        #[serde(rename = "$value")]
19        value: String,
20    },
21
22    #[serde(rename = "err")]
23    Err { code: String, msg: String },
24}
25
26impl UploadXMLAnswer {
27    fn to_result(self) -> Result<String, FlickrError> {
28        match self.content {
29            UploadXMLPayload::PhotoId { value } => Ok(value),
30            UploadXMLPayload::Err { code, msg } => Err(FlickrError {
31                stat: self.stat,
32                code: code.parse().unwrap_or(0),
33                message: msg,
34            }),
35        }
36    }
37}
38
39impl PhotoRequestBuilder {
40    /// Access the "special" upload API and upload a photo from a given path
41    pub async fn upload_from_path(&self, path: &std::path::Path) -> Result<String, Box<dyn Error>> {
42        self.upload(
43            &read(path).await?,
44            Some(String::from(
45                path.file_name()
46                    .and_then(|f| f.to_str())
47                    .unwrap_or("unknown"),
48            )),
49        )
50        .await
51    }
52
53    /// Access the "special" upload API and upload a photo from its contents
54    pub async fn upload(
55        &self,
56        photo: &[u8],
57        filename: Option<String>,
58    ) -> Result<String, Box<dyn Error>> {
59        let mut params = vec![];
60        oauth::build_request(
61            oauth::RequestTarget::Post(URL_UPLOAD),
62            &mut params,
63            &self.handle.key,
64            self.handle.token.as_ref(),
65        );
66
67        // Filename is apparently required and request will fail if not set
68        let photo_part =
69            Part::bytes(Vec::from(photo)).file_name(filename.unwrap_or("unknown".to_string()));
70
71        let form = params
72            .into_iter()
73            .fold(Form::new(), |form, (k, v)| form.text(k, v))
74            .part("photo", photo_part);
75
76        let request = self
77            .handle
78            .client
79            .post(URL_UPLOAD)
80            .multipart(form)
81            .send()
82            .await?;
83
84        let text = request.text().await?;
85
86        log::trace!("Upload response: {:?}", text);
87
88        let id = serde_xml_rs::from_str::<UploadXMLAnswer>(&text)?.to_result()?;
89
90        Ok(id)
91    }
92}
93
94#[test]
95fn test_upload_answer_error() {
96    let anwser = r#"<?xml version="1.0" encoding="utf-8" ?><rsp stat="fail"><err code="5" msg="Filetype was not recognised"/></rsp>"#;
97
98    let answer = serde_xml_rs::from_str::<UploadXMLAnswer>(anwser).unwrap();
99
100    assert_eq!(
101        answer,
102        UploadXMLAnswer {
103            stat: "fail".to_string(),
104            content: UploadXMLPayload::Err {
105                code: "5".to_string(),
106                msg: "Filetype was not recognised".to_string()
107            }
108        }
109    );
110}
111
112#[test]
113fn test_upload_answer_ok() {
114    let anwser = r#"<?xml version="1.0" encoding="utf-8" ?><rsp stat="ok"><photoid>54026462270</photoid></rsp>"#;
115
116    let answer = serde_xml_rs::from_str::<UploadXMLAnswer>(anwser).unwrap();
117
118    assert_eq!(
119        answer,
120        UploadXMLAnswer {
121            stat: "ok".to_string(),
122            content: UploadXMLPayload::PhotoId {
123                value: "54026462270".to_string()
124            }
125        }
126    );
127}