pinboard_rs/api/
raw.rs

1// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
2// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
3// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
4// option. This file may not be copied, modified, or distributed
5// except according to those terms.
6
7use async_trait::async_trait;
8use http::{header, Request};
9
10use crate::api::{query, ApiError, AsyncClient, AsyncQuery, Client, Endpoint, Query};
11
12/// A query modifier that returns the raw data from the endpoint.
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct Raw<E> {
15    endpoint: E,
16}
17
18/// Return the raw data from the endpoint.
19pub fn raw<E>(endpoint: E) -> Raw<E> {
20    Raw { endpoint }
21}
22
23impl<E, C> Query<Vec<u8>, C> for Raw<E>
24where
25    E: Endpoint,
26    C: Client,
27{
28    fn query(&self, client: &C) -> Result<Vec<u8>, ApiError<C::Error>> {
29        let mut url = client.rest_endpoint(&self.endpoint.endpoint())?;
30        self.endpoint.parameters().add_to_url(&mut url);
31
32        let req = Request::builder()
33            .method(self.endpoint.method())
34            .uri(query::url_to_http_uri(url));
35        let (req, data) = if let Some((mime, data)) = self.endpoint.body()? {
36            let req = req.header(header::CONTENT_TYPE, mime);
37            (req, data)
38        } else {
39            (req, Vec::new())
40        };
41        let rsp = client.rest(req, data)?;
42        if !rsp.status().is_success() {
43            let v = if let Ok(v) = serde_json::from_slice(rsp.body()) {
44                v
45            } else {
46                return Err(ApiError::server_error(rsp.status(), rsp.body()));
47            };
48            return Err(ApiError::from_pinboard(v));
49        }
50
51        Ok(rsp.into_body().as_ref().into())
52    }
53}
54
55#[async_trait]
56impl<E, C> AsyncQuery<Vec<u8>, C> for Raw<E>
57where
58    E: Endpoint + Sync,
59    C: AsyncClient + Sync,
60{
61    async fn query_async(&self, client: &C) -> Result<Vec<u8>, ApiError<C::Error>> {
62        let mut url = client.rest_endpoint(&self.endpoint.endpoint())?;
63        self.endpoint.parameters().add_to_url(&mut url);
64
65        let req = Request::builder()
66            .method(self.endpoint.method())
67            .uri(query::url_to_http_uri(url));
68        let (req, data) = if let Some((mime, data)) = self.endpoint.body()? {
69            let req = req.header(header::CONTENT_TYPE, mime);
70            (req, data)
71        } else {
72            (req, Vec::new())
73        };
74        let rsp = client.rest_async(req, data).await?;
75        if !rsp.status().is_success() {
76            let v = if let Ok(v) = serde_json::from_slice(rsp.body()) {
77                v
78            } else {
79                return Err(ApiError::server_error(rsp.status(), rsp.body()));
80            };
81            return Err(ApiError::from_pinboard(v));
82        }
83
84        Ok(rsp.into_body().as_ref().into())
85    }
86}
87
88#[cfg(test)]
89mod tests {
90    use http::StatusCode;
91    use serde_json::json;
92
93    use crate::api::endpoint_prelude::*;
94    use crate::api::{self, ApiError, AsyncQuery, Query};
95    use crate::test::client::{ExpectedUrl, SingleTestClient};
96
97    struct Dummy;
98
99    impl Endpoint for Dummy {
100        fn method(&self) -> Method {
101            Method::GET
102        }
103
104        fn endpoint(&self) -> Cow<'static, str> {
105            "dummy".into()
106        }
107    }
108
109    #[test]
110    fn test_pinboard_non_json_response() {
111        let endpoint = ExpectedUrl::builder().endpoint("dummy").build().unwrap();
112        let client = SingleTestClient::new_raw(endpoint, "not json");
113
114        let data = api::raw(Dummy).query(&client).unwrap();
115        itertools::assert_equal(data, "not json".bytes());
116    }
117
118    #[tokio::test]
119    async fn test_pinboard_non_json_response_async() {
120        let endpoint = ExpectedUrl::builder().endpoint("dummy").build().unwrap();
121        let client = SingleTestClient::new_raw(endpoint, "not json");
122
123        let data = api::raw(Dummy).query_async(&client).await.unwrap();
124        itertools::assert_equal(data, "not json".bytes());
125    }
126
127    #[test]
128    fn test_pinboard_error_bad_json() {
129        let endpoint = ExpectedUrl::builder()
130            .endpoint("dummy")
131            .status(StatusCode::NOT_FOUND)
132            .build()
133            .unwrap();
134        let client = SingleTestClient::new_raw(endpoint, "");
135
136        let err = api::raw(Dummy).query(&client).unwrap_err();
137        if let ApiError::PinboardService { status, .. } = err {
138            assert_eq!(status, http::StatusCode::NOT_FOUND);
139        } else {
140            panic!("unexpected error: {}", err);
141        }
142    }
143
144    #[test]
145    fn test_pinboard_error_detection() {
146        let endpoint = ExpectedUrl::builder()
147            .endpoint("dummy")
148            .status(StatusCode::NOT_FOUND)
149            .build()
150            .unwrap();
151        let client = SingleTestClient::new_json(
152            endpoint,
153            &json!({
154                "error_message": "dummy error message",
155            }),
156        );
157
158        let err = api::raw(Dummy).query(&client).unwrap_err();
159        if let ApiError::Pinboard { msg } = err {
160            assert_eq!(msg, "dummy error message");
161        } else {
162            panic!("unexpected error: {}", err);
163        }
164    }
165
166    #[test]
167    fn test_pinboard_error_detection_legacy() {
168        let endpoint = ExpectedUrl::builder()
169            .endpoint("dummy")
170            .status(StatusCode::NOT_FOUND)
171            .build()
172            .unwrap();
173        let client = SingleTestClient::new_json(
174            endpoint,
175            &json!({
176                "error_message": "dummy error message",
177            }),
178        );
179
180        let err = api::raw(Dummy).query(&client).unwrap_err();
181        if let ApiError::Pinboard { msg } = err {
182            assert_eq!(msg, "dummy error message");
183        } else {
184            panic!("unexpected error: {}", err);
185        }
186    }
187
188    #[test]
189    fn test_pinboard_error_detection_unknown() {
190        let endpoint = ExpectedUrl::builder()
191            .endpoint("dummy")
192            .status(StatusCode::NOT_FOUND)
193            .build()
194            .unwrap();
195        let err_obj = json!({
196            "bogus": "dummy error message",
197        });
198        let client = SingleTestClient::new_json(endpoint, &err_obj);
199
200        let err = api::raw(Dummy).query(&client).unwrap_err();
201        if let ApiError::PinboardUnrecognized { obj } = err {
202            assert_eq!(obj, err_obj);
203        } else {
204            panic!("unexpected error: {}", err);
205        }
206    }
207}