1use crate::api::endpoint_prelude::*;
8
9#[derive(Debug, Clone)]
11pub struct SudoContext<'a> {
12 sudo: Cow<'a, str>,
14}
15
16impl<'a> SudoContext<'a> {
17 pub fn new<S>(sudo: S) -> Self
19 where
20 S: Into<Cow<'a, str>>,
21 {
22 SudoContext {
23 sudo: sudo.into(),
24 }
25 }
26
27 pub fn apply<E>(&self, endpoint: E) -> Sudo<'a, E> {
29 Sudo {
30 endpoint,
31 sudo: self.sudo.clone(),
32 }
33 }
34}
35
36#[derive(Debug, Clone)]
38pub struct Sudo<'a, E> {
39 endpoint: E,
41
42 sudo: Cow<'a, str>,
44}
45
46pub fn sudo<'a, E, S>(endpoint: E, sudo: S) -> Sudo<'a, E>
48where
49 S: Into<Cow<'a, str>>,
50{
51 Sudo {
52 endpoint,
53 sudo: sudo.into(),
54 }
55}
56
57impl<E> Endpoint for Sudo<'_, E>
58where
59 E: Endpoint,
60{
61 fn method(&self) -> Method {
62 self.endpoint.method()
63 }
64
65 fn endpoint(&self) -> Cow<'static, str> {
66 self.endpoint.endpoint()
67 }
68
69 fn parameters(&self) -> QueryParams<'_> {
70 let mut params = self.endpoint.parameters();
71 params.push("sudo", &self.sudo);
72 params
73 }
74
75 fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
76 self.endpoint.body()
77 }
78}
79
80impl<E> Pageable for Sudo<'_, E>
81where
82 E: Pageable,
83{
84 fn use_keyset_pagination(&self) -> bool {
85 self.endpoint.use_keyset_pagination()
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use http::StatusCode;
92 use serde::Deserialize;
93 use serde_json::json;
94 use url::Url;
95
96 use crate::api::endpoint_prelude::*;
97 use crate::api::{self, ApiError, Query, SudoContext};
98 use crate::test::client::{ExpectedUrl, SingleTestClient};
99
100 struct Dummy;
101
102 impl Endpoint for Dummy {
103 fn method(&self) -> Method {
104 Method::GET
105 }
106
107 fn endpoint(&self) -> Cow<'static, str> {
108 "dummy".into()
109 }
110 }
111
112 #[derive(Debug, Deserialize)]
113 struct DummyResult {
114 value: u8,
115 }
116
117 #[test]
118 fn test_gitlab_non_json_response() {
119 let endpoint = ExpectedUrl::builder()
120 .endpoint("dummy")
121 .add_query_params(&[("sudo", "user")])
122 .build()
123 .unwrap();
124 let client = SingleTestClient::new_raw(endpoint, "not json");
125
126 let res: Result<DummyResult, _> = api::sudo(Dummy, "user").query(&client);
127 let err = res.unwrap_err();
128 if let ApiError::GitlabService {
129 status, ..
130 } = err
131 {
132 assert_eq!(status, StatusCode::OK);
133 } else {
134 panic!("unexpected error: {}", err);
135 }
136 }
137
138 #[test]
139 fn test_gitlab_empty_response() {
140 let endpoint = ExpectedUrl::builder()
141 .endpoint("dummy")
142 .add_query_params(&[("sudo", "user")])
143 .build()
144 .unwrap();
145 let client = SingleTestClient::new_raw(endpoint, "");
146
147 let res: Result<DummyResult, _> = api::sudo(Dummy, "user").query(&client);
148 let err = res.unwrap_err();
149 if let ApiError::GitlabService {
150 status, ..
151 } = err
152 {
153 assert_eq!(status, StatusCode::OK);
154 } else {
155 panic!("unexpected error: {}", err);
156 }
157 }
158
159 #[test]
160 fn test_gitlab_error_bad_json() {
161 let endpoint = ExpectedUrl::builder()
162 .endpoint("dummy")
163 .add_query_params(&[("sudo", "user")])
164 .status(StatusCode::NOT_FOUND)
165 .build()
166 .unwrap();
167 let client = SingleTestClient::new_raw(endpoint, "");
168
169 let res: Result<DummyResult, _> = api::sudo(Dummy, "user").query(&client);
170 let err = res.unwrap_err();
171 if let ApiError::GitlabService {
172 status, ..
173 } = err
174 {
175 assert_eq!(status, StatusCode::NOT_FOUND);
176 } else {
177 panic!("unexpected error: {}", err);
178 }
179 }
180
181 #[test]
182 fn test_gitlab_error_detection() {
183 let endpoint = ExpectedUrl::builder()
184 .endpoint("dummy")
185 .add_query_params(&[("sudo", "user")])
186 .status(StatusCode::NOT_FOUND)
187 .build()
188 .unwrap();
189 let client = SingleTestClient::new_json(
190 endpoint,
191 &json!({
192 "message": "dummy error message",
193 }),
194 );
195
196 let res: Result<DummyResult, _> = api::sudo(Dummy, "user").query(&client);
197 let err = res.unwrap_err();
198 if let ApiError::GitlabWithStatus {
199 status,
200 msg,
201 } = err
202 {
203 assert_eq!(status, StatusCode::NOT_FOUND);
204 assert_eq!(msg, "dummy error message");
205 } else {
206 panic!("unexpected error: {}", err);
207 }
208 }
209
210 #[test]
211 fn test_gitlab_error_detection_legacy() {
212 let endpoint = ExpectedUrl::builder()
213 .endpoint("dummy")
214 .add_query_params(&[("sudo", "user")])
215 .status(StatusCode::NOT_FOUND)
216 .build()
217 .unwrap();
218 let client = SingleTestClient::new_json(
219 endpoint,
220 &json!({
221 "error": "dummy error message",
222 }),
223 );
224
225 let res: Result<DummyResult, _> = api::sudo(Dummy, "user").query(&client);
226 let err = res.unwrap_err();
227 if let ApiError::GitlabWithStatus {
228 status,
229 msg,
230 } = err
231 {
232 assert_eq!(status, StatusCode::NOT_FOUND);
233 assert_eq!(msg, "dummy error message");
234 } else {
235 panic!("unexpected error: {}", err);
236 }
237 }
238
239 #[test]
240 fn test_gitlab_error_detection_unknown() {
241 let endpoint = ExpectedUrl::builder()
242 .endpoint("dummy")
243 .add_query_params(&[("sudo", "user")])
244 .status(StatusCode::NOT_FOUND)
245 .build()
246 .unwrap();
247 let err_obj = json!({
248 "bogus": "dummy error message",
249 });
250 let client = SingleTestClient::new_json(endpoint, &err_obj);
251
252 let res: Result<DummyResult, _> = api::sudo(Dummy, "user").query(&client);
253 let err = res.unwrap_err();
254 if let ApiError::GitlabUnrecognizedWithStatus {
255 status,
256 obj,
257 } = err
258 {
259 assert_eq!(status, StatusCode::NOT_FOUND);
260 assert_eq!(obj, err_obj);
261 } else {
262 panic!("unexpected error: {}", err);
263 }
264 }
265
266 #[test]
267 fn test_bad_deserialization() {
268 let endpoint = ExpectedUrl::builder()
269 .endpoint("dummy")
270 .add_query_params(&[("sudo", "user")])
271 .build()
272 .unwrap();
273 let client = SingleTestClient::new_json(
274 endpoint,
275 &json!({
276 "not_value": 0,
277 }),
278 );
279
280 let res: Result<DummyResult, _> = api::sudo(Dummy, "user").query(&client);
281 let err = res.unwrap_err();
282 if let ApiError::DataType {
283 source,
284 typename,
285 } = err
286 {
287 assert_eq!(source.to_string(), "missing field `value`");
288 assert_eq!(typename, "gitlab::api::sudo::tests::DummyResult");
289 } else {
290 panic!("unexpected error: {}", err);
291 }
292 }
293
294 #[test]
295 fn test_good_deserialization() {
296 let endpoint = ExpectedUrl::builder()
297 .endpoint("dummy")
298 .add_query_params(&[("sudo", "user")])
299 .build()
300 .unwrap();
301 let client = SingleTestClient::new_json(
302 endpoint,
303 &json!({
304 "value": 0,
305 }),
306 );
307
308 let res: DummyResult = api::sudo(Dummy, "user").query(&client).unwrap();
309 assert_eq!(res.value, 0);
310 }
311
312 #[test]
313 fn test_sudo_context() {
314 let endpoint = ExpectedUrl::builder()
315 .endpoint("dummy")
316 .add_query_params(&[("sudo", "user")])
317 .build()
318 .unwrap();
319 let client = SingleTestClient::new_json(
320 endpoint,
321 &json!({
322 "value": 0,
323 }),
324 );
325 let sudo_ctx = SudoContext::new("user");
326 let endpoint = sudo_ctx.apply(Dummy);
327
328 let res: DummyResult = endpoint.query(&client).unwrap();
329 assert_eq!(res.value, 0);
330 }
331
332 #[test]
333 fn test_sudo_use_keyset_pagination() {
334 let with_keyset = api::projects::jobs::Jobs::builder()
335 .project(1)
336 .build()
337 .unwrap();
338 let without_keyset = api::projects::deploy_keys::DeployKeys::builder()
339 .project(1)
340 .build()
341 .unwrap();
342
343 assert!(api::sudo(with_keyset, "user").use_keyset_pagination());
344 assert!(!api::sudo(without_keyset, "user").use_keyset_pagination());
345 }
346
347 struct DummyWithParameters;
348
349 impl Endpoint for DummyWithParameters {
350 fn method(&self) -> Method {
351 Method::GET
352 }
353
354 fn endpoint(&self) -> Cow<'static, str> {
355 "dummy/params".into()
356 }
357
358 fn parameters(&self) -> QueryParams<'_> {
359 let mut params = QueryParams::default();
360 params.push("dummy", true);
361 params
362 }
363
364 fn body(&self) -> Result<Option<(&'static str, Vec<u8>)>, BodyError> {
365 let mut params = FormParams::default();
366 params.push("dummy", true);
367 params.into_body()
368 }
369 }
370
371 #[test]
372 fn test_sudo_parameters() {
373 let endpoint = DummyWithParameters;
374 let sudo_endpoint = api::sudo(endpoint, "user");
375
376 let params = sudo_endpoint.parameters();
377 let mut url = Url::parse("https://example.com").unwrap();
378 params.add_to_url(&mut url);
379 itertools::assert_equal(
380 url.query_pairs(),
381 [
382 ("dummy".into(), "true".into()),
383 ("sudo".into(), "user".into()),
384 ],
385 );
386 }
387
388 #[test]
389 fn test_sudo_body() {
390 let endpoint = DummyWithParameters;
391 let sudo_endpoint = api::sudo(endpoint, "user");
392
393 let params = sudo_endpoint.body().unwrap();
394 if let Some((mime, bytes)) = params {
395 assert_eq!(mime, "application/x-www-form-urlencoded");
396 assert_eq!(bytes, b"dummy=true");
397 } else {
398 panic!("expected to have `dummy=true`");
399 }
400 }
401}