use bytes::Bytes;
use connectrpc::client::{BoxFuture, ClientBody, ClientTransport};
use http::{Request, Response};
use http_body_util::BodyExt;
use crate::CloudClient;
#[derive(Debug, thiserror::Error)]
pub enum CloudTransportError {
#[error("failed to read request body: {0}")]
Body(#[source] Box<dyn std::error::Error + Send + Sync>),
#[error("failed to build request: {0}")]
Build(#[source] reqwest::Error),
#[error(transparent)]
Olai(#[from] crate::Error),
}
#[derive(Clone)]
pub struct CloudTransport(CloudClient);
impl CloudTransport {
pub fn new(client: CloudClient) -> Self {
Self(client)
}
pub fn client(&self) -> &CloudClient {
&self.0
}
}
impl From<CloudClient> for CloudTransport {
fn from(client: CloudClient) -> Self {
Self(client)
}
}
impl ClientTransport for CloudTransport {
type ResponseBody = reqwest::Body;
type Error = CloudTransportError;
fn send(
&self,
request: Request<ClientBody>,
) -> BoxFuture<'static, Result<Response<Self::ResponseBody>, Self::Error>> {
let client = self.0.clone();
Box::pin(async move {
let (parts, body) = request.into_parts();
let bytes: Bytes = body
.collect()
.await
.map_err(|e| CloudTransportError::Body(Box::new(e)))?
.to_bytes();
let http_req = Request::from_parts(parts, bytes);
let reqwest_req =
reqwest::Request::try_from(http_req).map_err(CloudTransportError::Build)?;
let response = client.sign_and_send(reqwest_req).await?;
Ok(Response::from(response))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use connectrpc::client::full_body;
use http_body_util::BodyExt;
#[tokio::test]
async fn cloud_transport_signs_and_round_trips() {
let mut server = mockito::Server::new_async().await;
let mock = server
.mock("POST", "/svc.Service/Method")
.match_header("authorization", "Bearer tok")
.match_body("request-bytes")
.with_status(200)
.with_body("response-bytes")
.expect(1)
.create_async()
.await;
let transport = CloudTransport::new(crate::CloudClient::new_with_token("tok"));
let request = Request::builder()
.method(http::Method::POST)
.uri(format!("{}/svc.Service/Method", server.url()))
.header("content-type", "application/proto")
.body(full_body(Bytes::from_static(b"request-bytes")))
.unwrap();
let response = transport.send(request).await.unwrap();
assert_eq!(response.status(), 200);
let body = response.into_body().collect().await.unwrap().to_bytes();
assert_eq!(&body[..], b"response-bytes");
mock.assert_async().await;
}
}