use connectrpc::client::{BoxFuture, ClientBody, ClientTransport};
use http::uri::{Authority, PathAndQuery, Scheme};
use http::{Request, Response, Uri};
use worker::send::{SendFuture, SendWrapper};
use worker::{Body, Fetch, Fetcher};
#[derive(Clone)]
pub struct FetcherTransport {
fetcher: SendWrapper<Fetcher>,
}
impl FetcherTransport {
pub fn new(fetcher: Fetcher) -> Self {
Self {
fetcher: SendWrapper::new(fetcher),
}
}
}
impl std::fmt::Debug for FetcherTransport {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("FetcherTransport").finish_non_exhaustive()
}
}
impl ClientTransport for FetcherTransport {
type ResponseBody = Body;
type Error = worker::Error;
fn send(
&self,
request: Request<ClientBody>,
) -> BoxFuture<'static, Result<Response<Self::ResponseBody>, Self::Error>> {
let fetcher = (*self.fetcher).clone();
Box::pin(SendFuture::new(async move {
fetcher.fetch_request(request).await
}))
}
}
#[derive(Clone, Debug)]
pub struct FetchTransport {
scheme: Scheme,
authority: Authority,
}
impl FetchTransport {
pub fn new(base: Uri) -> Result<Self, worker::Error> {
let parts = base.into_parts();
let scheme = parts.scheme.ok_or_else(|| {
worker::Error::RustError("FetchTransport base URI is missing a scheme".into())
})?;
let authority = parts.authority.ok_or_else(|| {
worker::Error::RustError("FetchTransport base URI is missing an authority".into())
})?;
Ok(Self { scheme, authority })
}
}
impl ClientTransport for FetchTransport {
type ResponseBody = Body;
type Error = worker::Error;
fn send(
&self,
request: Request<ClientBody>,
) -> BoxFuture<'static, Result<Response<Self::ResponseBody>, Self::Error>> {
let scheme = self.scheme.clone();
let authority = self.authority.clone();
Box::pin(SendFuture::new(async move {
let request = rewrite_uri(request, scheme, authority)?;
let req = worker::Request::try_from(request)?;
Fetch::Request(req).send().await.and_then(|resp| {
let resp: http::Response<Body> = resp.try_into()?;
Ok(resp)
})
}))
}
}
fn rewrite_uri<B>(
mut req: http::Request<B>,
scheme: Scheme,
authority: Authority,
) -> Result<http::Request<B>, worker::Error> {
let path_and_query = req
.uri()
.path_and_query()
.cloned()
.unwrap_or_else(|| PathAndQuery::from_static("/"));
let new_uri = Uri::builder()
.scheme(scheme)
.authority(authority)
.path_and_query(path_and_query)
.build()
.map_err(|e| worker::Error::RustError(format!("rewrite uri: {e}")))?;
*req.uri_mut() = new_uri;
Ok(req)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fetch_transport_rejects_uri_without_scheme() {
let uri: Uri = "/some/path".parse().unwrap();
let err = FetchTransport::new(uri).unwrap_err();
assert!(format!("{err}").contains("scheme"));
}
#[test]
fn fetch_transport_rejects_uri_without_authority() {
let uri: Uri = "mailto:user@example.com".parse().unwrap();
let err = FetchTransport::new(uri).unwrap_err();
let msg = format!("{err}");
assert!(
msg.contains("authority") || msg.contains("scheme"),
"unexpected error: {msg}"
);
}
#[test]
fn fetch_transport_accepts_https_uri() {
let uri: Uri = "https://example.com:8443/base".parse().unwrap();
let t = FetchTransport::new(uri).expect("valid base URI");
assert_eq!(t.scheme, Scheme::HTTPS);
assert_eq!(t.authority.as_str(), "example.com:8443");
}
#[test]
fn rewrite_uri_replaces_scheme_and_authority_keeps_path() {
let req: http::Request<()> = http::Request::builder()
.uri("http://placeholder.invalid/foo/bar?x=1")
.body(())
.unwrap();
let scheme = Scheme::HTTPS;
let authority: Authority = "real.example:8443".parse().unwrap();
let rewritten = rewrite_uri(req, scheme, authority).unwrap();
let uri = rewritten.uri();
assert_eq!(uri.scheme_str(), Some("https"));
assert_eq!(
uri.authority().map(|a| a.as_str()),
Some("real.example:8443")
);
assert_eq!(uri.path(), "/foo/bar");
assert_eq!(uri.query(), Some("x=1"));
}
#[test]
fn rewrite_uri_defaults_path_to_root_when_missing() {
let req: http::Request<()> = http::Request::builder()
.uri("http://placeholder")
.body(())
.unwrap();
let scheme = Scheme::HTTP;
let authority: Authority = "real.example".parse().unwrap();
let rewritten = rewrite_uri(req, scheme, authority).unwrap();
assert_eq!(rewritten.uri().path(), "/");
}
#[test]
fn transports_implement_client_transport() {
fn assert_transport<T: ClientTransport>() {}
assert_transport::<FetcherTransport>();
assert_transport::<FetchTransport>();
}
}