shuttle_api_client/
util.rs1use std::fmt::Debug;
2
3use anyhow::{Context, Result};
4use async_trait::async_trait;
5use bytes::Bytes;
6use http::StatusCode;
7use serde::de::DeserializeOwned;
8use shuttle_common::models::error::ApiError;
9
10#[async_trait]
12pub trait ToBodyContent {
13 async fn to_json<T: DeserializeOwned>(self) -> Result<ParsedJson<T>>;
14 async fn to_text(self) -> Result<String>;
15 async fn to_bytes(self) -> Result<Bytes>;
16 async fn to_empty(self) -> Result<()>;
17}
18
19fn into_api_error(body: &str, status_code: StatusCode) -> ApiError {
20 #[cfg(feature = "tracing")]
21 tracing::trace!("Parsing response as API error");
22
23 let res: ApiError = match serde_json::from_str(body) {
24 Ok(res) => res,
25 _ => ApiError::new(
26 format!("Failed to parse error response from the server:\n{}", body),
27 status_code,
28 ),
29 };
30
31 res
32}
33
34fn bytes_to_string_with_fallback(bytes: Bytes) -> String {
36 String::from_utf8(bytes.to_vec()).unwrap_or_else(|_| format!("[{} bytes]", bytes.len()))
37}
38
39pub struct ParsedJson<T> {
40 inner: T,
41 pub raw_json: String,
42}
43
44impl<T> ParsedJson<T> {
45 pub fn into_inner(self) -> T {
46 self.inner
47 }
48 pub fn into_parts(self) -> (T, String) {
49 (self.inner, self.raw_json)
50 }
51}
52
53impl<T> AsRef<T> for ParsedJson<T> {
54 fn as_ref(&self) -> &T {
55 &self.inner
56 }
57}
58
59impl<T: Debug> Debug for ParsedJson<T> {
60 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
61 self.inner.fmt(f)
62 }
63}
64impl<T: std::fmt::Display> std::fmt::Display for ParsedJson<T> {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 self.inner.fmt(f)
67 }
68}
69
70#[async_trait]
71impl ToBodyContent for reqwest::Response {
72 async fn to_json<T: DeserializeOwned>(self) -> Result<ParsedJson<T>> {
73 let status_code = self.status();
74 let bytes = self.bytes().await?;
75 let string = bytes_to_string_with_fallback(bytes);
76
77 #[cfg(feature = "tracing")]
78 tracing::trace!(response = %string, "Parsing response as JSON");
79
80 if status_code.is_client_error() || status_code.is_server_error() {
81 return Err(into_api_error(&string, status_code).into());
82 }
83
84 let t = serde_json::from_str(&string).context("failed to parse a successful response")?;
85
86 Ok(ParsedJson {
87 inner: t,
88 raw_json: string,
89 })
90 }
91
92 async fn to_text(self) -> Result<String> {
93 let status_code = self.status();
94 let bytes = self.bytes().await?;
95 let string = bytes_to_string_with_fallback(bytes);
96
97 #[cfg(feature = "tracing")]
98 tracing::trace!(response = %string, "Parsing response as text");
99
100 if status_code.is_client_error() || status_code.is_server_error() {
101 return Err(into_api_error(&string, status_code).into());
102 }
103
104 Ok(string)
105 }
106
107 async fn to_bytes(self) -> Result<Bytes> {
108 let status_code = self.status();
109 let bytes = self.bytes().await?;
110
111 #[cfg(feature = "tracing")]
112 tracing::trace!(response_length = bytes.len(), "Got response bytes");
113
114 if status_code.is_client_error() || status_code.is_server_error() {
115 let string = bytes_to_string_with_fallback(bytes);
116 return Err(into_api_error(&string, status_code).into());
117 }
118
119 Ok(bytes)
120 }
121
122 async fn to_empty(self) -> Result<()> {
123 let status_code = self.status();
124
125 if status_code.is_client_error() || status_code.is_server_error() {
126 let bytes = self.bytes().await?;
127 let string = bytes_to_string_with_fallback(bytes);
128 return Err(into_api_error(&string, status_code).into());
129 }
130
131 Ok(())
132 }
133}