use chrono::Utc;
use serde::{Deserialize, Serialize};
use crate::error::{ProviderError, Result};
use crate::http_client::HttpUtils;
use crate::traits::{ErrorContext, ProviderErrorMapper, RawApiError};
use super::{
ALIYUN_DNS_HOST, ALIYUN_DNS_VERSION, AliyunProvider, EMPTY_BODY_SHA256,
serialize_to_query_string,
};
impl AliyunProvider {
pub(crate) async fn request<T: for<'de> Deserialize<'de>, B: Serialize>(
&self,
action: &str,
params: &B,
ctx: ErrorContext,
) -> Result<T> {
let query_string = serialize_to_query_string(params)?;
let timestamp = Utc::now().format("%Y-%m-%dT%H:%M:%SZ").to_string();
let nonce = uuid::Uuid::new_v4().to_string();
let authorization = self.sign(action, &query_string, ×tamp, &nonce);
let url = if query_string.is_empty() {
format!("https://{ALIYUN_DNS_HOST}/")
} else {
format!("https://{ALIYUN_DNS_HOST}/?{query_string}")
};
let request = self
.client
.post(&url)
.header("Host", ALIYUN_DNS_HOST)
.header("x-acs-action", action)
.header("x-acs-version", ALIYUN_DNS_VERSION)
.header("x-acs-date", ×tamp)
.header("x-acs-signature-nonce", &nonce)
.header("x-acs-content-sha256", EMPTY_BODY_SHA256)
.header("Authorization", authorization);
let (status, response_text) = HttpUtils::execute_request_with_retry(
request,
self.provider_name(),
"POST",
&format!("{url} (Action: {action})"),
self.max_retries,
)
.await?;
if status >= 400 {
if let Ok(value) = serde_json::from_str::<serde_json::Value>(&response_text)
&& let (Some(code), Some(message)) = (
value.get("Code").and_then(|v| v.as_str()),
value.get("Message").and_then(|v| v.as_str()),
)
{
log::error!("API error: {code} - {message}");
return Err(self.map_error(RawApiError::with_code(code, message), ctx));
}
return Err(ProviderError::NetworkError {
provider: self.provider_name().to_string(),
detail: format!("HTTP {status}: {response_text}"),
});
}
let value: serde_json::Value = HttpUtils::parse_json(&response_text, self.provider_name())?;
if let (Some(code), Some(message)) = (
value.get("Code").and_then(|v| v.as_str()),
value.get("Message").and_then(|v| v.as_str()),
) {
log::error!("API error: {code} - {message}");
return Err(self.map_error(RawApiError::with_code(code, message), ctx));
}
serde_json::from_value(value).map_err(|e| ProviderError::ParseError {
provider: self.provider_name().to_string(),
detail: e.to_string(),
})
}
}