1use crate::{
7 error::{Error, ErrorKind},
8 http::{headers::ERROR_CODE, AsyncRawResponse, RawResponse, StatusCode},
9};
10use serde::{Deserialize, Serialize};
11use std::{collections::HashMap, future::Future, str};
12
13#[derive(Clone, Debug, Deserialize, Serialize)]
31#[serde(rename_all = "camelCase")]
32pub struct ErrorResponse {
33 pub error: Option<ErrorDetail>,
35}
36
37impl TryFrom<Error> for ErrorResponse {
38 type Error = Error;
39
40 fn try_from(value: Error) -> Result<Self, Self::Error> {
41 match value.kind() {
42 ErrorKind::HttpResponse { raw_response, .. } => {
43 let error_response: Option<crate::Result<ErrorResponse>> = raw_response
44 .as_ref()
45 .map(|raw| serde_json::from_slice(raw.body().as_ref()).map_err(Error::from));
46 match error_response {
47 Some(result) => Ok(result?),
48 None => Err(value),
49 }
50 }
51 _ => Err(value),
52 }
53 }
54}
55
56#[derive(Clone, Debug, Deserialize, Serialize)]
60#[serde(rename_all = "camelCase")]
61pub struct ErrorDetail {
62 pub code: Option<String>,
64
65 pub message: Option<String>,
67
68 pub target: Option<String>,
70
71 #[serde(default)]
73 pub details: Vec<ErrorDetail>,
74
75 #[serde(rename = "innererror")]
77 pub inner_error: Option<InnerError>,
78
79 #[serde(flatten)]
81 pub additional_properties: HashMap<String, crate::Value>,
82}
83
84#[derive(Clone, Debug, Deserialize, Serialize)]
88#[serde(rename_all = "camelCase")]
89pub struct InnerError {
90 pub code: Option<String>,
92
93 #[serde(rename = "innererror")]
95 pub inner_error: Option<Box<InnerError>>,
96}
97
98#[derive(Debug, Deserialize)]
100struct ErrorResponseInternal<'a> {
101 #[serde(borrow)]
102 error: ErrorDetailsInternal<'a>,
103}
104
105#[derive(Debug, Deserialize)]
106struct ErrorDetailsInternal<'a> {
107 code: Option<&'a str>,
108 message: Option<&'a str>,
109}
110
111pub trait Response: crate::private::Sealed {
115 fn status(&self) -> StatusCode;
117
118 fn try_into_raw_response(self) -> impl Future<Output = crate::Result<RawResponse>>;
120}
121
122impl crate::private::Sealed for AsyncRawResponse {}
123impl crate::private::Sealed for RawResponse {}
124
125impl Response for AsyncRawResponse {
126 fn status(&self) -> StatusCode {
127 self.status()
128 }
129
130 fn try_into_raw_response(self) -> impl Future<Output = crate::Result<RawResponse>> {
131 self.try_into_raw_response()
132 }
133}
134
135impl Response for RawResponse {
136 fn status(&self) -> StatusCode {
137 self.status()
138 }
139
140 #[inline]
141 fn try_into_raw_response(self) -> impl Future<Output = crate::Result<RawResponse>> {
142 std::future::ready(Ok(self))
143 }
144}
145
146#[derive(Debug, Default)]
148pub struct CheckSuccessOptions {
149 pub success_codes: &'static [u16],
153}
154
155pub async fn check_success<T: Response>(
167 response: T,
168 options: Option<CheckSuccessOptions>,
169) -> crate::Result<T> {
170 let status = response.status();
171
172 if options
173 .as_ref()
174 .map(|o| {
175 if o.success_codes.is_empty() {
176 status.is_success()
177 } else {
178 o.success_codes.contains(&status)
179 }
180 })
181 .unwrap_or_else(|| status.is_success())
182 {
183 return Ok(response);
184 }
185
186 let raw_response = response.try_into_raw_response().await?;
187
188 if raw_response.body().is_empty() {
190 let error_code = raw_response
191 .headers()
192 .get_optional_str(&ERROR_CODE)
193 .map(str::to_owned);
194 let error_kind = ErrorKind::HttpResponse {
195 status,
196 error_code,
197 raw_response: Some(Box::new(raw_response)),
198 };
199 return Err(Error::with_message(error_kind, status.to_string()));
200 }
201 let internal_response =
202 serde_json::de::from_slice::<ErrorResponseInternal>(raw_response.body())
203 .map_err(Error::from);
204
205 let internal_response = match internal_response {
206 Ok(r) => r,
207 Err(_) => {
208 let error_code = raw_response
210 .headers()
211 .get_optional_str(&ERROR_CODE)
212 .map_or_else(|| raw_response.status().to_string(), str::to_owned);
213 let message = str::from_utf8(raw_response.body())
214 .unwrap_or("(invalid utf-8 in body)")
215 .to_string();
216 let error_kind = ErrorKind::HttpResponse {
217 status,
218 error_code: Some(error_code),
219 raw_response: Some(Box::new(raw_response)),
220 };
221 return Err(Error::with_message(
222 error_kind,
223 format!("{}: {}", status, message),
224 ));
225 }
226 };
227
228 let error_code = raw_response
230 .headers()
231 .get_optional_str(&ERROR_CODE)
232 .or(internal_response.error.code)
233 .map(str::to_owned);
234 let message = internal_response
235 .error
236 .message
237 .map_or_else(|| status.to_string(), str::to_owned);
238 let error_kind = ErrorKind::HttpResponse {
239 status,
240 error_code,
241 raw_response: Some(Box::new(raw_response)),
242 };
243
244 Err(Error::with_message(error_kind, message))
245}
246
247#[cfg(test)]
248mod tests {
249 use super::*;
250 use crate::http::{headers, headers::Headers, StatusCode};
251 use crate::Bytes;
252
253 #[tokio::test]
254 async fn matching_against_http_error() {
255 let mut headers = Headers::new();
256 headers.insert(headers::CONTENT_TYPE, "application/json".to_string());
257 let response = AsyncRawResponse::from_bytes(
258 StatusCode::ImATeapot,
259 headers,
260 Bytes::from_static(br#"{"error": {"code":"teapot","message":"I'm a teapot"}}"#),
261 );
262
263 let err = check_success(response, None).await.unwrap_err();
264 let kind = err.kind();
265 assert!(matches!(
266 kind,
267 ErrorKind::HttpResponse {
268 status: StatusCode::ImATeapot,
269 error_code,
270 raw_response: Some(_),
271 }
272 if error_code.as_deref() == Some("teapot")
273 ));
274 }
275
276 #[tokio::test]
277 async fn matching_against_custom_http_error_empty_set() {
278 let mut headers = Headers::new();
279 headers.insert(headers::CONTENT_TYPE, "application/json".to_string());
280 let response = AsyncRawResponse::from_bytes(
281 StatusCode::ImATeapot,
282 headers,
283 Bytes::from_static(br#"{"error": {"code":"teapot","message":"I'm a teapot"}}"#),
284 );
285
286 let err = check_success(response, Some(CheckSuccessOptions { success_codes: &[] }))
287 .await
288 .unwrap_err();
289 let kind = err.kind();
290 assert!(matches!(
291 kind,
292 ErrorKind::HttpResponse {
293 status: StatusCode::ImATeapot,
294 error_code,
295 raw_response: Some(_),
296 }
297 if error_code.as_deref() == Some("teapot")
298 ));
299 }
300
301 #[tokio::test]
302 async fn matching_against_custom_http_error_in_set() {
303 let mut headers = Headers::new();
304 headers.insert(headers::CONTENT_TYPE, "application/json".to_string());
305 let response = AsyncRawResponse::from_bytes(
306 StatusCode::ImATeapot,
307 headers,
308 Bytes::from_static(br#"{"error": {"code":"teapot","message":"I'm a teapot"}}"#),
309 );
310
311 let _ = check_success(
312 response,
313 Some(CheckSuccessOptions {
314 success_codes: &[418],
315 }),
316 )
317 .await
318 .expect("Should be a success return");
319 }
320
321 #[tokio::test]
322 async fn matching_against_custom_http_error_in_set_success_should_fail() {
323 let mut headers = Headers::new();
324 headers.insert(headers::CONTENT_TYPE, "application/json".to_string());
325 let response = AsyncRawResponse::from_bytes(
326 StatusCode::Ok,
327 headers,
328 Bytes::from_static(br#"{"error": {"code":"teapot","message":"I'm a teapot"}}"#),
329 );
330
331 let err = check_success(
332 response,
333 Some(CheckSuccessOptions {
334 success_codes: &[418],
335 }),
336 )
337 .await
338 .expect_err("Should be a failure return");
339 let kind = err.kind();
340 assert!(matches!(
341 kind,
342 ErrorKind::HttpResponse {
343 status: StatusCode::Ok,
344 error_code,
345 raw_response: Some(_),
346 }
347 if error_code.as_deref() == Some("teapot")
348 ));
349 }
350
351 #[tokio::test]
352 async fn matching_against_http_error_no_body() {
353 let mut headers = Headers::new();
354 headers.insert(headers::ERROR_CODE, "testError".to_string());
355 let response = AsyncRawResponse::from_bytes(StatusCode::ImATeapot, headers, Bytes::new());
356
357 let err = check_success(response, None).await.unwrap_err();
358 let kind = err.kind();
359 assert!(matches!(
360 kind,
361 ErrorKind::HttpResponse {
362 status: StatusCode::ImATeapot,
363 error_code,
364 raw_response: Some(_),
365 }
366 if error_code.as_deref() == Some("testError")
367 ));
368 }
369
370 #[tokio::test]
371 async fn matching_against_http_error_invalid_body() {
372 let mut headers = Headers::new();
373 headers.insert(headers::ERROR_CODE, "testError".to_string());
374 let response = AsyncRawResponse::from_bytes(
375 StatusCode::ImATeapot,
376 headers,
377 Bytes::from_static(br#"{"json": "error"}"#),
378 );
379
380 let err = check_success(response, None).await.unwrap_err();
381 let ErrorKind::HttpResponse {
382 status,
383 error_code: Some(error_code),
384 raw_response: Some(raw_response),
385 } = err.kind()
386 else {
387 panic!("expected ErrorKind::HttpResponse");
388 };
389
390 assert!(err.to_string().contains(r#"{"json": "error"}"#));
391 assert_eq!(status, &StatusCode::ImATeapot);
392 assert_eq!(error_code, "testError");
393 assert_eq!(raw_response.status(), StatusCode::ImATeapot);
394 assert_eq!(raw_response.headers().iter().count(), 1);
395 assert!(
396 matches!(str::from_utf8(raw_response.body()), Ok(body) if body == r#"{"json": "error"}"#)
397 );
398 }
399
400 #[test]
401 fn deserialize_to_error_response() {
402 let err : ErrorResponse = serde_json::from_slice (br#"{"error":{"code":"InvalidRequest","message":"The request object is not recognized.","innererror":{"code":"InvalidKey"},"key":"foo"}}"#)
403 .expect("Parse success.");
404 err.error.as_ref().expect("error should be set");
405
406 println!("{:?}", err);
407 assert_eq!(
408 err.error.as_ref().unwrap().code,
409 Some("InvalidRequest".to_string())
410 );
411 assert_eq!(
412 err.error.as_ref().unwrap().message,
413 Some("The request object is not recognized.".to_string())
414 );
415 assert!(err.error.as_ref().unwrap().inner_error.is_some());
416 assert_eq!(
417 err.error
418 .as_ref()
419 .unwrap()
420 .inner_error
421 .as_ref()
422 .unwrap()
423 .code,
424 Some("InvalidKey".to_string())
425 );
426 assert!(err
427 .error
428 .as_ref()
429 .unwrap()
430 .additional_properties
431 .contains_key("key"));
432 }
433
434 #[test]
435 fn serialize_error_response() {
436 let error_response = ErrorResponse {
437 error: Some(ErrorDetail {
438 code: Some("InvalidRequest".to_string()),
439 message: Some("The request object is not recognized.".to_string()),
440 target: None,
441 details: vec![],
442 inner_error: Some(InnerError {
443 code: Some("InvalidKey".to_string()),
444 inner_error: None,
445 }),
446 additional_properties: HashMap::from([(
447 "key".to_string(),
448 crate::Value::from("foo"),
449 )]),
450 }),
451 };
452
453 let json = serde_json::to_value(&error_response).expect("Serialize success.");
454 let error_obj = json.get("error").expect("error should be set");
455 assert_eq!(
456 error_obj.get("code").and_then(|v| v.as_str()),
457 Some("InvalidRequest")
458 );
459 assert_eq!(
460 error_obj.get("message").and_then(|v| v.as_str()),
461 Some("The request object is not recognized.")
462 );
463 assert_eq!(
464 error_obj
465 .get("innererror")
466 .and_then(|v| v.get("code"))
467 .and_then(|v| v.as_str()),
468 Some("InvalidKey")
469 );
470 assert_eq!(error_obj.get("key").and_then(|v| v.as_str()), Some("foo"));
471 assert!(error_obj.get("target").is_some());
472 assert!(error_obj.get("details").is_some());
473 }
474
475 #[tokio::test]
476 async fn convert_error_to_error_response() -> crate::Result<()> {
477 {
478 let err: Error = Error::from(ErrorKind::HttpResponse {
479 status: StatusCode::BadRequest,
480 error_code: Some("testError".to_string()),
481 raw_response: None,
482 });
483 let _error_response = ErrorResponse::try_from(err)
484 .expect_err("expected an error because there is no raw_response");
485 }
486 {
487 let buf_response = AsyncRawResponse::from_bytes(
488 StatusCode::BadRequest,
489 Headers::new(),
490 Bytes::from_static(br#"{"error":{"code":"InvalidRequest","message":"The request object is not recognized.","innererror":{"code":"InvalidKey"},"key":"foo"}}"#),
491 );
492 let err: Error = Error::from(ErrorKind::HttpResponse {
493 status: StatusCode::BadRequest,
494 error_code: Some("testError".to_string()),
495 raw_response: Some(Box::new(buf_response.try_into_raw_response().await?)),
496 });
497 let error_response = ErrorResponse::try_from(err).expect("expected an ErrorResponse");
498 error_response.error.as_ref().expect("error should be set");
499 println!("{:?}", error_response);
500 assert_eq!(
501 error_response.error.as_ref().unwrap().code,
502 Some("InvalidRequest".to_string())
503 );
504 }
505 Ok(())
506 }
507
508 #[tokio::test]
509 async fn convert_buf_response_to_error_response() -> crate::Result<()> {
510 {
511 let buf_response = AsyncRawResponse::from_bytes(
512 StatusCode::BadRequest,
513 Headers::new(),
514 Bytes::from_static(br#"{"error":{"code":"InvalidRequest","message":"The request object is not recognized.","innererror":{"code":"InvalidKey"},"key":"foo"}}"#),
515 );
516 let error_response: ErrorResponse = buf_response
517 .try_into_raw_response()
518 .await?
519 .into_body()
520 .json()
521 .expect("expected an ErrorResponse");
522 error_response.error.as_ref().expect("error should be set");
523 println!("{:?}", error_response);
524 assert_eq!(
525 error_response.error.as_ref().unwrap().code,
526 Some("InvalidRequest".to_string())
527 );
528 }
529 Ok(())
530 }
531
532 #[test]
533 fn clone_error_response() {
534 let response: ErrorResponse = serde_json::from_slice(
535 br#"{"error":{"code":"InvalidRequest","message":"bad request","innererror":{"code":"InvalidKey"},"extra":"value"}}"#,
536 )
537 .expect("deserialize");
538 let cloned = response.clone();
539 let detail = cloned.error.as_ref().expect("error detail present");
540 assert_eq!(detail.code.as_deref(), Some("InvalidRequest"));
541 assert_eq!(detail.message.as_deref(), Some("bad request"));
542 assert_eq!(
543 detail
544 .inner_error
545 .as_ref()
546 .and_then(|ie| ie.code.as_deref()),
547 Some("InvalidKey"),
548 );
549 assert!(detail.additional_properties.contains_key("extra"));
550 }
551
552 #[tokio::test]
553 async fn deserialize_to_error_response_internal() {
554 let err :ErrorResponseInternal = serde_json::from_slice (br#"{"error":{"code":"InvalidRequest","message":"The request object is not recognized.","innererror":{"code":"InvalidKey","key":"foo"}}}"#)
555 .expect("Parse success.");
556 println!("{:?}", err);
557
558 assert_eq!(err.error.code, Some("InvalidRequest"));
559 assert_eq!(
560 err.error.message,
561 Some("The request object is not recognized.")
562 );
563 }
564}