1use async_trait::async_trait;
8use http::{header, Request};
9
10use crate::api::{query, ApiError, AsyncClient, AsyncQuery, Client, Endpoint, Query};
11
12#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub struct Raw<E> {
15 endpoint: E,
16}
17
18pub 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}