use super::*;
#[cfg_attr(nightly, doc(cfg(feature = "surf")))]
#[derive(Debug, displaydoc::Display, thiserror::Error)]
pub enum SurfError {
Surf(surf::Error),
InvalidHeaderValue(#[from] http::header::InvalidHeaderValue),
InvalidHeaderName(#[from] http::header::InvalidHeaderName),
UrlError(#[from] url::ParseError),
}
use surf::Client as SurfClient;
#[cfg(feature = "surf")]
fn http1_to_surf(m: &http::Method) -> surf::http::Method {
match *m {
http::Method::GET => surf::http::Method::Get,
http::Method::CONNECT => http_types::Method::Connect,
http::Method::DELETE => http_types::Method::Delete,
http::Method::HEAD => http_types::Method::Head,
http::Method::OPTIONS => http_types::Method::Options,
http::Method::PATCH => http_types::Method::Patch,
http::Method::POST => http_types::Method::Post,
http::Method::PUT => http_types::Method::Put,
http::Method::TRACE => http_types::Method::Trace,
_ => unimplemented!(),
}
}
#[cfg_attr(nightly, doc(cfg(feature = "surf")))] impl Client for SurfClient {
type Error = SurfError;
fn req(&self, request: Request) -> BoxedFuture<'static, Result<Response, Self::Error>> {
let method = http1_to_surf(request.method());
let url = match url::Url::parse(&request.uri().to_string()) {
Ok(url) => url,
Err(err) => return Box::pin(async move { Err(err.into()) }),
};
let mut req = surf::Request::new(method, url);
for (name, value) in request.headers().iter() {
let value =
match surf::http::headers::HeaderValue::from_bytes(value.as_bytes().to_vec())
.map_err(SurfError::Surf)
{
Ok(val) => val,
Err(err) => return Box::pin(async { Err(err) }),
};
req.append_header(name.as_str(), value);
}
let client = self.clone();
Box::pin(async move {
req.body_bytes(request.into_body());
let mut response = client.send(req).await.map_err(SurfError::Surf)?;
let mut result = http::Response::builder().status(
http::StatusCode::from_u16(response.status().into())
.expect("http_types::StatusCode only contains valid status codes"),
);
let mut response_headers: http::header::HeaderMap = response
.iter()
.map(|(k, v)| {
Ok((
http::header::HeaderName::from_bytes(k.as_str().as_bytes())?,
http::HeaderValue::from_str(v.as_str())?,
))
})
.collect::<Result<_, SurfError>>()?;
let _ = std::mem::replace(&mut result.headers_mut(), Some(&mut response_headers));
let result = if let Some(v) = response.version() {
result.version(match v {
surf::http::Version::Http0_9 => http::Version::HTTP_09,
surf::http::Version::Http1_0 => http::Version::HTTP_10,
surf::http::Version::Http1_1 => http::Version::HTTP_11,
surf::http::Version::Http2_0 => http::Version::HTTP_2,
surf::http::Version::Http3_0 => http::Version::HTTP_3,
_ => http::Version::HTTP_11,
})
} else {
result
};
Ok(result
.body(
response
.take_body()
.into_bytes()
.await
.map_err(SurfError::Surf)
.map(Into::into)?,
)
.expect("mismatch surf -> http conversion should not fail"))
})
}
}
#[cfg_attr(nightly, doc(cfg(feature = "surf")))]
#[derive(Debug, displaydoc::Display, thiserror::Error)]
pub enum SurfClientDefaultError {
SurfError(surf::Error),
}
impl ClientDefault<'static> for SurfClient
where Self: Default
{
type Error = SurfClientDefaultError;
fn default_client_with_name(product: Option<http::HeaderValue>) -> Result<Self, Self::Error> {
use std::str::FromStr as _;
struct SurfAgentMiddleware {
user_agent: surf::http::headers::HeaderValue,
}
#[async_trait::async_trait]
impl surf::middleware::Middleware for SurfAgentMiddleware {
async fn handle(
&self,
req: surf::Request,
client: SurfClient,
next: surf::middleware::Next<'_>,
) -> surf::Result<surf::Response> {
let mut req = req;
req.set_header(surf::http::headers::USER_AGENT, self.user_agent.clone());
next.run(req, client).await
}
}
let client = Self::default();
let user_agent = if let Some(product) = product {
let mut user_agent = product.as_bytes().to_owned();
user_agent.push(b' ');
user_agent.extend(TWITCH_API_USER_AGENT.as_bytes());
surf::http::headers::HeaderValue::from_bytes(user_agent)
.map_err(SurfClientDefaultError::SurfError)?
} else {
surf::http::headers::HeaderValue::from_str(TWITCH_API_USER_AGENT)
.map_err(SurfClientDefaultError::SurfError)?
};
let middleware = SurfAgentMiddleware { user_agent };
Ok(client.with(middleware))
}
}
#[cfg(test)]
mod tests {
#[test]
fn surf() {
use super::ClientDefault;
use std::convert::TryInto;
super::SurfClient::default_client_with_name(Some("test/123".try_into().unwrap())).unwrap();
super::SurfClient::default_client();
}
}