use std::fmt::Write;
use chrono::{DateTime, Utc};
use reqwest::header::HeaderMap;
use serde::de::DeserializeOwned;
use super::fmt::HttpCase;
use super::jose::Nonce;
use super::request::Encode;
use super::AcmeError;
use super::Url;
use jaws::fmt;
pub trait Decode: Sized {
fn decode(data: &[u8]) -> Result<Self, AcmeError>;
}
impl<T> Decode for T
where
T: DeserializeOwned,
{
fn decode(data: &[u8]) -> Result<Self, AcmeError> {
serde_json::from_slice(data).map_err(AcmeError::de)
}
}
#[derive(Debug, Clone)]
pub struct Response<T> {
url: Url,
status: reqwest::StatusCode,
headers: reqwest::header::HeaderMap,
payload: T,
}
impl<T> Response<T>
where
T: Decode,
{
pub(crate) async fn from_decoded_response(
response: reqwest::Response,
) -> Result<Self, AcmeError> {
let url = response.url().clone().into();
let status = response.status();
let headers = response.headers().clone();
let body = response.bytes().await?;
let payload: T = T::decode(&body)?;
Ok(Response {
url,
status,
headers,
payload,
})
}
}
impl<T> Response<T> {
pub fn status(&self) -> reqwest::StatusCode {
self.status
}
pub fn url(&self) -> &Url {
&self.url
}
pub fn headers(&self) -> &HeaderMap {
&self.headers
}
pub fn retry_after(&self) -> Option<std::time::Duration> {
self.headers()
.get(reqwest::header::RETRY_AFTER)
.and_then(|v| v.to_str().ok())
.and_then(|v| {
if v.contains("GMT") {
DateTime::parse_from_rfc2822(v)
.map(|ts| ts.signed_duration_since(Utc::now()))
.ok()
.and_then(|d| d.to_std().ok())
} else {
v.parse::<u64>().ok().map(std::time::Duration::from_secs)
}
})
}
pub fn nonce(&self) -> Option<Nonce> {
super::client::extract_nonce(&self.headers).ok()
}
pub fn location(&self) -> Option<Url> {
self.headers.get(reqwest::header::LOCATION).map(|value| {
value
.to_str()
.unwrap_or_else(|_| {
panic!(
"valid text encoding in {} header",
reqwest::header::LOCATION
)
})
.parse()
.unwrap_or_else(|_| panic!("valid URL in {} header", reqwest::header::LOCATION))
})
}
pub fn content_type(&self) -> Option<mime::Mime> {
self.headers.get(reqwest::header::CONTENT_TYPE).map(|v| {
v.to_str()
.unwrap_or_else(|_| {
panic!(
"valid text encoding in {} header",
reqwest::header::CONTENT_TYPE
)
})
.parse()
.unwrap_or_else(|_| {
panic!(
"valid MIME type in {} header",
reqwest::header::CONTENT_TYPE
)
})
})
}
pub fn payload(&self) -> &T {
&self.payload
}
pub fn into_inner(self) -> T {
self.payload
}
}
impl<T> fmt::JWTFormat for Response<T>
where
T: Encode,
{
fn fmt<W: fmt::Write>(&self, f: &mut fmt::IndentWriter<'_, W>) -> fmt::Result {
writeln!(
f,
"HTTP/1.1 {} {}",
self.status.as_u16(),
self.status.canonical_reason().unwrap_or("")
)?;
for (header, value) in self.headers.iter() {
writeln!(f, "{}: {}", header.titlecase(), value.to_str().unwrap())?;
}
writeln!(f)?;
write!(f, "{}", self.payload.encode().unwrap())
}
}