use crate::core::CoreError;
use serde::Deserialize;
#[derive(Debug, thiserror::Error)]
pub enum MessagingError {
#[error(transparent)]
Core(#[from] CoreError),
#[error("HTTP request failed: {0}")]
Http(#[from] reqwest::Error),
#[error("FCM API error ({status}): {message}")]
Api {
status: u16,
message: String,
error_code: Option<String>,
},
#[error("batch size {actual} exceeds the maximum of {max} messages/tokens per call")]
BatchTooLarge {
actual: usize,
max: usize,
},
}
#[derive(Debug, Deserialize)]
struct GoogleApiErrorBody {
error: GoogleApiError,
}
#[derive(Debug, Deserialize)]
struct GoogleApiError {
message: String,
status: Option<String>,
#[serde(default)]
details: Vec<GoogleApiErrorDetail>,
}
#[derive(Debug, Deserialize)]
struct GoogleApiErrorDetail {
#[serde(rename = "errorCode")]
error_code: Option<String>,
}
pub(crate) async fn parse_fcm_response<T: serde::de::DeserializeOwned>(
response: reqwest::Response,
) -> Result<T, MessagingError> {
if !response.status().is_success() {
let status = response.status().as_u16();
let body = response
.text()
.await
.unwrap_or_else(|_| "<no response body>".to_string());
return Err(MessagingError::from_api_response(status, &body));
}
response
.json::<T>()
.await
.map_err(|e| MessagingError::Core(CoreError::Http(e)))
}
impl MessagingError {
fn from_api_response(status: u16, body: &str) -> Self {
let (message, error_code) = match serde_json::from_str::<GoogleApiErrorBody>(body) {
Ok(parsed) => {
let code = parsed
.error
.details
.iter()
.find_map(|d| d.error_code.clone())
.or_else(|| parsed.error.status.clone());
(parsed.error.message, code)
}
Err(_) => (body.to_string(), None),
};
MessagingError::Api {
status,
message,
error_code,
}
}
}