use serde::{Deserialize, Serialize};
use thiserror::Error;
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Error, Debug)]
pub enum Error {
#[error("HTTP request failed: {0}")]
Request(#[from] reqwest::Error),
#[error("JSON error: {0}")]
Json(#[from] serde_json::Error),
#[error("URL parse error: {0}")]
UrlParse(#[from] url::ParseError),
#[error("API error: {message} (code: {code})")]
Api {
code: i32,
message: String,
error_subcode: Option<i32>,
error_data: Option<ApiErrorData>,
},
#[error("Rate limit exceeded. Retry after {retry_after:?} seconds")]
RateLimited { retry_after: Option<u64> },
#[error("Invalid or expired access token")]
InvalidToken,
#[error("Media upload failed: {0}")]
MediaUpload(String),
#[error("Invalid phone number format: {0}")]
InvalidPhoneNumber(String),
#[error("Message not sent: {0}")]
MessageNotSent(String),
#[error("IO error: {0}")]
Io(#[from] std::io::Error),
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiErrorData {
pub messaging_product: Option<String>,
pub details: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiErrorResponse {
pub error: ApiError,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ApiError {
pub message: String,
#[serde(rename = "type")]
pub error_type: Option<String>,
pub code: i32,
pub error_subcode: Option<i32>,
pub error_user_title: Option<String>,
pub error_user_msg: Option<String>,
pub fbtrace_id: Option<String>,
pub error_data: Option<ApiErrorData>,
}
impl From<ApiErrorResponse> for Error {
fn from(response: ApiErrorResponse) -> Self {
let err = response.error;
match err.code {
190 => Error::InvalidToken,
4 | 17 | 32 | 613 => Error::RateLimited { retry_after: None },
_ => Error::Api {
code: err.code,
message: err.message,
error_subcode: err.error_subcode,
error_data: err.error_data,
},
}
}
}