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 {
21 endpoint,
22 }
23}
24
25impl<E, C> Query<Vec<u8>, C> for Raw<E>
26where
27 E: Endpoint,
28 C: Client,
29{
30 fn query(&self, client: &C) -> Result<Vec<u8>, ApiError<C::Error>> {
31 let mut url = self
32 .endpoint
33 .url_base()
34 .endpoint_for(client, &self.endpoint.endpoint())?;
35 self.endpoint.parameters().add_to_url(&mut url);
36
37 let req = Request::builder()
38 .method(self.endpoint.method())
39 .uri(query::url_to_http_uri(url));
40 let (req, data) = if let Some((mime, data)) = self.endpoint.body()? {
41 let req = req.header(header::CONTENT_TYPE, mime);
42 (req, data)
43 } else {
44 (req, Vec::new())
45 };
46 let rsp = client.rest(req, data)?;
47 let status = rsp.status();
48 if !status.is_success() {
49 let v = if let Ok(v) = serde_json::from_slice(rsp.body()) {
50 v
51 } else {
52 return Err(ApiError::server_error(status, rsp.body()));
53 };
54 return Err(ApiError::from_gitlab_with_status(status, v));
55 } else if status == http::StatusCode::MOVED_PERMANENTLY {
56 return Err(ApiError::moved_permanently(
57 rsp.headers().get(http::header::LOCATION),
58 ));
59 }
60
61 Ok(rsp.into_body().as_ref().into())
62 }
63}
64
65#[async_trait]
66impl<E, C> AsyncQuery<Vec<u8>, C> for Raw<E>
67where
68 E: Endpoint + Sync,
69 C: AsyncClient + Sync,
70{
71 async fn query_async(&self, client: &C) -> Result<Vec<u8>, ApiError<C::Error>> {
72 let mut url = self
73 .endpoint
74 .url_base()
75 .endpoint_for(client, &self.endpoint.endpoint())?;
76 self.endpoint.parameters().add_to_url(&mut url);
77
78 let req = Request::builder()
79 .method(self.endpoint.method())
80 .uri(query::url_to_http_uri(url));
81 let (req, data) = if let Some((mime, data)) = self.endpoint.body()? {
82 let req = req.header(header::CONTENT_TYPE, mime);
83 (req, data)
84 } else {
85 (req, Vec::new())
86 };
87 let rsp = client.rest_async(req, data).await?;
88 let status = rsp.status();
89 if !status.is_success() {
90 let v = if let Ok(v) = serde_json::from_slice(rsp.body()) {
91 v
92 } else {
93 return Err(ApiError::server_error(status, rsp.body()));
94 };
95 return Err(ApiError::from_gitlab_with_status(status, v));
96 } else if status == http::StatusCode::MOVED_PERMANENTLY {
97 return Err(ApiError::moved_permanently(
98 rsp.headers().get(http::header::LOCATION),
99 ));
100 }
101
102 Ok(rsp.into_body().as_ref().into())
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use http::StatusCode;
109 use serde_json::json;
110
111 use crate::api::endpoint_prelude::*;
112 use crate::api::{self, ApiError, AsyncQuery, Query};
113 use crate::test::client::{ExpectedUrl, SingleTestClient};
114
115 struct Dummy;
116
117 impl Endpoint for Dummy {
118 fn method(&self) -> Method {
119 Method::GET
120 }
121
122 fn endpoint(&self) -> Cow<'static, str> {
123 "dummy".into()
124 }
125 }
126
127 #[test]
128 fn test_gitlab_non_json_response() {
129 let endpoint = ExpectedUrl::builder().endpoint("dummy").build().unwrap();
130 let client = SingleTestClient::new_raw(endpoint, "not json");
131
132 let data = api::raw(Dummy).query(&client).unwrap();
133 itertools::assert_equal(data, "not json".bytes());
134 }
135
136 #[tokio::test]
137 async fn test_gitlab_non_json_response_async() {
138 let endpoint = ExpectedUrl::builder().endpoint("dummy").build().unwrap();
139 let client = SingleTestClient::new_raw(endpoint, "not json");
140
141 let data = api::raw(Dummy).query_async(&client).await.unwrap();
142 itertools::assert_equal(data, "not json".bytes());
143 }
144
145 #[test]
146 fn test_gitlab_error_bad_json() {
147 let endpoint = ExpectedUrl::builder()
148 .endpoint("dummy")
149 .status(StatusCode::NOT_FOUND)
150 .build()
151 .unwrap();
152 let client = SingleTestClient::new_raw(endpoint, "");
153
154 let err = api::raw(Dummy).query(&client).unwrap_err();
155 if let ApiError::GitlabService {
156 status, ..
157 } = err
158 {
159 assert_eq!(status, StatusCode::NOT_FOUND);
160 } else {
161 panic!("unexpected error: {}", err);
162 }
163 }
164
165 #[test]
166 fn test_gitlab_error_detection() {
167 let endpoint = ExpectedUrl::builder()
168 .endpoint("dummy")
169 .status(StatusCode::NOT_FOUND)
170 .build()
171 .unwrap();
172 let client = SingleTestClient::new_json(
173 endpoint,
174 &json!({
175 "message": "dummy error message",
176 }),
177 );
178
179 let err = api::raw(Dummy).query(&client).unwrap_err();
180 if let ApiError::GitlabWithStatus {
181 status,
182 msg,
183 } = err
184 {
185 assert_eq!(status, StatusCode::NOT_FOUND);
186 assert_eq!(msg, "dummy error message");
187 } else {
188 panic!("unexpected error: {}", err);
189 }
190 }
191
192 #[test]
193 fn test_gitlab_error_detection_legacy() {
194 let endpoint = ExpectedUrl::builder()
195 .endpoint("dummy")
196 .status(StatusCode::NOT_FOUND)
197 .build()
198 .unwrap();
199 let client = SingleTestClient::new_json(
200 endpoint,
201 &json!({
202 "error": "dummy error message",
203 }),
204 );
205
206 let err = api::raw(Dummy).query(&client).unwrap_err();
207 if let ApiError::GitlabWithStatus {
208 status,
209 msg,
210 } = err
211 {
212 assert_eq!(status, StatusCode::NOT_FOUND);
213 assert_eq!(msg, "dummy error message");
214 } else {
215 panic!("unexpected error: {}", err);
216 }
217 }
218
219 #[test]
220 fn test_gitlab_error_detection_unknown() {
221 let endpoint = ExpectedUrl::builder()
222 .endpoint("dummy")
223 .status(StatusCode::NOT_FOUND)
224 .build()
225 .unwrap();
226 let err_obj = json!({
227 "bogus": "dummy error message",
228 });
229 let client = SingleTestClient::new_json(endpoint, &err_obj);
230
231 let err = api::raw(Dummy).query(&client).unwrap_err();
232 if let ApiError::GitlabUnrecognizedWithStatus {
233 status,
234 obj,
235 } = err
236 {
237 assert_eq!(status, StatusCode::NOT_FOUND);
238 assert_eq!(obj, err_obj);
239 } else {
240 panic!("unexpected error: {}", err);
241 }
242 }
243}