use anyhow::{Context, anyhow};
use http::{HeaderValue, Method, header::CONTENT_TYPE};
use std::borrow::Cow;
use tracing::debug;
use crate::{FromBody, IntoBody as _, IntoResponse, Result, ToCodeMessage, auth::AliyunAuth};
pub use crate::auth::AccessKeySecret;
pub async fn call<R>(
auth: &dyn AliyunAuth,
http_client: &reqwest::Client,
version: &'static str,
endpoint: &'static str,
req: R,
) -> Result<<R::ResponseWrap as IntoResponse>::Response>
where
R: super::Request,
{
let path_args = req.get_path_args();
let url_path: Cow<'static, str> = if path_args.is_empty() {
R::URL_PATH.into()
} else {
let mut path = R::URL_PATH.to_string();
for (placeholder, value) in path_args.iter() {
path = path.replace(placeholder, value);
}
path.into()
};
let query_string = auth.canonical_query_string(req.to_query_params());
let custom_headers = req.to_headers();
let endpoint = req.process_endpoint(endpoint);
let resource_path = req.resource_path();
let body = req.to_body();
let content_type = body.content_type();
let body = body.into_body()?;
let mut headers = auth.create_headers(R::ACTION, version)?;
if let Some(content_type) = content_type {
headers.insert(CONTENT_TYPE, content_type);
}
for (name, value) in custom_headers {
let header_name = http::header::HeaderName::from_bytes(name.as_bytes())
.context("Invalid custom header name")?;
let header_value =
http::header::HeaderValue::from_str(&value).context("Invalid custom header value")?;
headers.insert(header_name, header_value);
}
let authorization = auth.sign(
&mut headers,
&url_path,
&query_string,
R::METHOD.as_str(),
&body,
resource_path.as_ref(),
)?;
headers.insert(
http::header::AUTHORIZATION,
HeaderValue::try_from(authorization).context("convert to header value")?,
);
let mut url = format!("https://{}{}", endpoint, url_path);
if !query_string.is_empty() {
url.push('?');
url.push_str(&query_string);
}
debug!("{}", url);
debug!("{:#?}", &headers);
let resp = match R::METHOD {
Method::POST => http_client.post(url).headers(headers).body(body),
Method::PUT => http_client.put(url).headers(headers).body(body),
Method::GET => http_client.get(url).headers(headers),
Method::DELETE => http_client.delete(url).headers(headers).body(body),
Method::HEAD => http_client.head(url).headers(headers),
_ => unreachable!(),
}
.send()
.await
.context("send request")?;
let status = resp.status();
let resp_headers = resp.headers().clone();
let resp_bytes = resp.bytes().await.context("Get response bytes")?;
let resp = if status.is_success() {
let mut wrap = R::ResponseWrap::from_body(resp_bytes.to_vec())?;
R::from_headers(&mut wrap, &resp_headers)?;
wrap.to_code_message().check()?;
wrap.into_response()
} else {
let resp_text = String::from_utf8_lossy(&resp_bytes);
match serde_json::from_str::<crate::CodeMessage>(&resp_text) {
Ok(code_msg) => return Err(code_msg.into()),
Err(_) => {
match quick_xml::de::from_str::<crate::CodeMessage>(&resp_text) {
Ok(code_msg) => return Err(code_msg.into()),
Err(_) => return Err(anyhow!("HTTP error: {} - {}", status, resp_text).into()),
}
}
}
};
Ok(resp)
}