use std::time::{SystemTime, UNIX_EPOCH};
use {
reqwest::Response,
serde::{
Deserialize, Deserializer,
de::{DeserializeOwned, Error},
},
serde_json::{
Value::{self, Null, Object, String as SerdeString},
from_str, from_value,
},
};
use crate::{
api::content::albums::Image,
errors::QobuzApiError::{self, ApiErrorResponse, ApiResponseParseError, ResourceNotFoundError},
};
pub async fn parse_response<T: DeserializeOwned>(
response: Response,
endpoint: &str,
) -> Result<T, QobuzApiError> {
let status = response.status();
let body = response.text().await?;
if status.is_success() {
return from_str::<T>(&body).map_err(|e| ApiResponseParseError {
content: truncate(&body, 500),
source_info: Some(format!("{e}")),
});
}
if let Ok(err) = from_str::<Value>(&body) {
let code = i32::try_from(err.get("code").and_then(Value::as_i64).unwrap_or_default())
.unwrap_or_default();
let message = err
.get("message")
.and_then(|v| v.as_str())
.unwrap_or("Unknown error");
if status.as_u16() == 404 {
return Err(ResourceNotFoundError {
resource_type: endpoint.trim_start_matches('/').to_string(),
resource_id: message.to_string(),
});
}
return Err(ApiErrorResponse {
code,
message: message.to_string(),
status: status.to_string(),
});
}
Err(ApiErrorResponse {
code: i32::from(status.as_u16()),
message: truncate(&body, 200),
status: status.to_string(),
})
}
#[must_use]
pub fn timestamp() -> String {
let now = SystemTime::now()
.duration_since(UNIX_EPOCH)
.unwrap_or_default();
now.as_secs().to_string()
}
#[must_use]
pub fn truncate(s: &str, max_len: usize) -> String {
if s.len() <= max_len {
s.to_string()
} else {
let mut truncated: String = s.chars().take(max_len).collect();
truncated.push_str("...");
truncated
}
}
fn deserialize_optional_value<'de, D>(deserializer: D) -> Result<Option<Value>, D::Error>
where
D: Deserializer<'de>,
{
Option::<Value>::deserialize(deserializer)
}
fn map_value_to_string(value: Option<Value>) -> Option<String> {
match value {
None | Some(Null) => None,
Some(SerdeString(s)) => Some(s),
Some(v) => Some(v.to_string()),
}
}
pub fn deserialize_flexible_name<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let value = deserialize_optional_value(deserializer)?;
match value {
None | Some(Null) => Ok(None),
Some(SerdeString(s)) => Ok(Some(s)),
Some(Object(map)) => map
.get("display")
.and_then(Value::as_str)
.map(|s| Some(s.to_string()))
.ok_or_else(|| Error::custom("object missing 'display' field")),
Some(v) => Ok(map_value_to_string(Some(v))),
}
}
pub fn deserialize_flexible_string_id<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
where
D: Deserializer<'de>,
{
let value = deserialize_optional_value(deserializer)?;
Ok(map_value_to_string(value))
}
pub fn deserialize_picture<'de, D>(deserializer: D) -> Result<Option<Image>, D::Error>
where
D: Deserializer<'de>,
{
let value = deserialize_optional_value(deserializer)?;
match value {
None | Some(Null | SerdeString(_)) => Ok(None),
Some(v) => from_value(v).map_err(Error::custom),
}
}