yacme_protocol/
response.rs1use std::fmt::Write;
13
14use chrono::{DateTime, Utc};
15use http::HeaderMap;
16use serde::de::DeserializeOwned;
17
18use crate::fmt::{self, HttpCase};
19use crate::jose::Nonce;
20use crate::request::Encode;
21use crate::AcmeError;
22use crate::Url;
23
24pub trait Decode: Sized {
31 fn decode(data: &[u8]) -> Result<Self, AcmeError>;
33}
34
35impl<T> Decode for T
36where
37 T: DeserializeOwned,
38{
39 fn decode(data: &[u8]) -> Result<Self, AcmeError> {
40 serde_json::from_slice(data).map_err(AcmeError::de)
41 }
42}
43
44#[derive(Debug, Clone)]
46pub struct Response<T> {
47 url: Url,
48 status: http::StatusCode,
49 headers: http::HeaderMap,
50 payload: T,
51}
52
53impl<T> Response<T>
54where
55 T: Decode,
56{
57 pub(crate) async fn from_decoded_response(
58 response: reqwest::Response,
59 ) -> Result<Self, AcmeError> {
60 let url = response.url().clone().into();
61 let status = response.status();
62 let headers = response.headers().clone();
63 let body = response.bytes().await?;
64 let payload: T = T::decode(&body)?;
65
66 Ok(Response {
67 url,
68 status,
69 headers,
70 payload,
71 })
72 }
73}
74
75impl<T> Response<T> {
76 pub fn status(&self) -> http::StatusCode {
78 self.status
79 }
80
81 pub fn url(&self) -> &Url {
83 &self.url
84 }
85
86 pub fn headers(&self) -> &HeaderMap {
88 &self.headers
89 }
90
91 pub fn retry_after(&self) -> Option<std::time::Duration> {
93 self.headers()
94 .get(http::header::RETRY_AFTER)
95 .and_then(|v| v.to_str().ok())
96 .and_then(|v| {
97 if v.contains("GMT") {
98 DateTime::parse_from_rfc2822(v)
99 .map(|ts| ts.signed_duration_since(Utc::now()))
100 .ok()
101 .and_then(|d| d.to_std().ok())
102 } else {
103 v.parse::<u64>().ok().map(std::time::Duration::from_secs)
104 }
105 })
106 }
107
108 pub fn nonce(&self) -> Option<Nonce> {
113 crate::client::extract_nonce(&self.headers).ok()
114 }
115
116 pub fn location(&self) -> Option<Url> {
118 self.headers.get(http::header::LOCATION).map(|value| {
119 value
120 .to_str()
121 .unwrap_or_else(|_| {
122 panic!("valid text encoding in {} header", http::header::LOCATION)
123 })
124 .parse()
125 .unwrap_or_else(|_| panic!("valid URL in {} header", http::header::LOCATION))
126 })
127 }
128
129 pub fn content_type(&self) -> Option<mime::Mime> {
131 self.headers.get(http::header::CONTENT_TYPE).map(|v| {
132 v.to_str()
133 .unwrap_or_else(|_| {
134 panic!(
135 "valid text encoding in {} header",
136 http::header::CONTENT_TYPE
137 )
138 })
139 .parse()
140 .unwrap_or_else(|_| {
141 panic!("valid MIME type in {} header", http::header::CONTENT_TYPE)
142 })
143 })
144 }
145
146 pub fn payload(&self) -> &T {
148 &self.payload
149 }
150
151 pub fn into_inner(self) -> T {
153 self.payload
154 }
155}
156
157impl<T> fmt::AcmeFormat for Response<T>
158where
159 T: Encode,
160{
161 fn fmt<W: fmt::Write>(&self, f: &mut fmt::IndentWriter<'_, W>) -> fmt::Result {
162 writeln!(
163 f,
164 "HTTP/1.1 {} {}",
165 self.status.as_u16(),
166 self.status.canonical_reason().unwrap_or("")
167 )?;
168 for (header, value) in self.headers.iter() {
169 writeln!(f, "{}: {}", header.titlecase(), value.to_str().unwrap())?;
170 }
171
172 writeln!(f)?;
173
174 write!(f, "{}", self.payload.encode().unwrap())
175 }
176}