qobuz_api/api/
response.rs1use std::time::{SystemTime, UNIX_EPOCH};
4
5use {
6 reqwest::Response,
7 serde::{
8 Deserialize, Deserializer,
9 de::{DeserializeOwned, Error},
10 },
11 serde_json::{
12 Value::{self, Null, Object, String as SerdeString},
13 from_str, from_value,
14 },
15};
16
17use crate::{
18 api::content::albums::Image,
19 errors::QobuzApiError::{self, ApiErrorResponse, ApiResponseParseError, ResourceNotFoundError},
20};
21
22pub async fn parse_response<T: DeserializeOwned>(
37 response: Response,
38 endpoint: &str,
39) -> Result<T, QobuzApiError> {
40 let status = response.status();
41 let body = response.text().await?;
42
43 if status.is_success() {
44 return from_str::<T>(&body).map_err(|e| ApiResponseParseError {
45 content: truncate(&body, 500),
46 source_info: Some(format!("{e}")),
47 });
48 }
49
50 if let Ok(err) = from_str::<Value>(&body) {
51 let code = i32::try_from(err.get("code").and_then(Value::as_i64).unwrap_or_default())
52 .unwrap_or_default();
53 let message = err
54 .get("message")
55 .and_then(|v| v.as_str())
56 .unwrap_or("Unknown error");
57
58 if status.as_u16() == 404 {
59 return Err(ResourceNotFoundError {
60 resource_type: endpoint.trim_start_matches('/').to_string(),
61 resource_id: message.to_string(),
62 });
63 }
64
65 return Err(ApiErrorResponse {
66 code,
67 message: message.to_string(),
68 status: status.to_string(),
69 });
70 }
71
72 Err(ApiErrorResponse {
73 code: i32::from(status.as_u16()),
74 message: truncate(&body, 200),
75 status: status.to_string(),
76 })
77}
78
79#[must_use]
85pub fn timestamp() -> String {
86 let now = SystemTime::now()
87 .duration_since(UNIX_EPOCH)
88 .unwrap_or_default();
89 now.as_secs().to_string()
90}
91
92#[must_use]
103pub fn truncate(s: &str, max_len: usize) -> String {
104 if s.len() <= max_len {
105 s.to_string()
106 } else {
107 let mut truncated: String = s.chars().take(max_len).collect();
108 truncated.push_str("...");
109 truncated
110 }
111}
112
113fn deserialize_optional_value<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
127where
128 D: Deserializer<'de>,
129{
130 Option::<Value>::deserialize(deserializer)
131}
132
133fn map_value_to_string(value: Option<Value>) -> Option<String> {
143 match value {
144 None | Some(Null) => None,
145 Some(SerdeString(s)) => Some(s),
146 Some(v) => Some(v.to_string()),
147 }
148}
149
150pub fn deserialize_flexible_name<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
165where
166 D: Deserializer<'de>,
167{
168 let value = deserialize_optional_value(deserializer)?;
169 match value {
170 None | Some(Null) => Ok(None),
171 Some(SerdeString(s)) => Ok(Some(s)),
172 Some(Object(map)) => map
173 .get("display")
174 .and_then(Value::as_str)
175 .map(|s| Some(s.to_string()))
176 .ok_or_else(|| Error::custom("object missing 'display' field")),
177 Some(v) => Ok(map_value_to_string(Some(v))),
178 }
179}
180
181pub fn deserialize_flexible_string_id<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
195where
196 D: Deserializer<'de>,
197{
198 let value = deserialize_optional_value(deserializer)?;
199 Ok(map_value_to_string(value))
200}
201
202pub fn deserialize_picture<'de, D>(deserializer: D) -> Result<Option<Image>, D::Error>
217where
218 D: Deserializer<'de>,
219{
220 let value = deserialize_optional_value(deserializer)?;
221 match value {
222 None | Some(Null | SerdeString(_)) => Ok(None),
223 Some(v) => from_value(v).map_err(Error::custom),
224 }
225}