connectrpc_workers/
lib.rs1use connectrpc::client::{BoxFuture, ClientBody, ClientTransport};
47use http::uri::{Authority, PathAndQuery, Scheme};
48use http::{Request, Response, Uri};
49use worker::send::{SendFuture, SendWrapper};
50use worker::{Body, Fetch, Fetcher, Stub};
51
52#[derive(Clone)]
57pub struct FetcherTransport {
58 fetcher: SendWrapper<Fetcher>,
59}
60
61impl FetcherTransport {
62 pub fn new(fetcher: Fetcher) -> Self {
63 Self {
64 fetcher: SendWrapper::new(fetcher),
65 }
66 }
67
68 pub fn from_stub(stub: Stub) -> Self {
83 Self::new(stub.into_rpc())
84 }
85}
86
87impl std::fmt::Debug for FetcherTransport {
88 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
89 f.debug_struct("FetcherTransport").finish_non_exhaustive()
90 }
91}
92
93impl ClientTransport for FetcherTransport {
94 type ResponseBody = Body;
95 type Error = worker::Error;
96
97 fn send(
98 &self,
99 request: Request<ClientBody>,
100 ) -> BoxFuture<'static, Result<Response<Self::ResponseBody>, Self::Error>> {
101 let fetcher = (*self.fetcher).clone();
103 Box::pin(SendFuture::new(async move {
104 fetcher.fetch_request(request).await
105 }))
106 }
107}
108
109#[derive(Clone, Debug)]
119pub struct FetchTransport {
120 scheme: Scheme,
121 authority: Authority,
122}
123
124impl FetchTransport {
125 pub fn new(base: Uri) -> Result<Self, worker::Error> {
126 let parts = base.into_parts();
127 let scheme = parts.scheme.ok_or_else(|| {
128 worker::Error::RustError("FetchTransport base URI is missing a scheme".into())
129 })?;
130 let authority = parts.authority.ok_or_else(|| {
131 worker::Error::RustError("FetchTransport base URI is missing an authority".into())
132 })?;
133 Ok(Self { scheme, authority })
134 }
135}
136
137impl ClientTransport for FetchTransport {
138 type ResponseBody = Body;
139 type Error = worker::Error;
140
141 fn send(
142 &self,
143 request: Request<ClientBody>,
144 ) -> BoxFuture<'static, Result<Response<Self::ResponseBody>, Self::Error>> {
145 let scheme = self.scheme.clone();
146 let authority = self.authority.clone();
147 Box::pin(SendFuture::new(async move {
148 let request = rewrite_uri(request, scheme, authority)?;
149 let req = worker::Request::try_from(request)?;
150 Fetch::Request(req).send().await.and_then(|resp| {
151 let resp: http::Response<Body> = resp.try_into()?;
152 Ok(resp)
153 })
154 }))
155 }
156}
157
158fn rewrite_uri<B>(
159 mut req: http::Request<B>,
160 scheme: Scheme,
161 authority: Authority,
162) -> Result<http::Request<B>, worker::Error> {
163 let path_and_query = req
164 .uri()
165 .path_and_query()
166 .cloned()
167 .unwrap_or_else(|| PathAndQuery::from_static("/"));
168 let new_uri = Uri::builder()
169 .scheme(scheme)
170 .authority(authority)
171 .path_and_query(path_and_query)
172 .build()
173 .map_err(|e| worker::Error::RustError(format!("rewrite uri: {e}")))?;
174 *req.uri_mut() = new_uri;
175 Ok(req)
176}
177
178#[cfg(test)]
179mod tests {
180 use super::*;
181
182 #[test]
183 fn fetch_transport_rejects_uri_without_scheme() {
184 let uri: Uri = "/some/path".parse().unwrap();
188 let err = FetchTransport::new(uri).unwrap_err();
189 assert!(format!("{err}").contains("scheme"));
190 }
191
192 #[test]
193 fn fetch_transport_rejects_uri_without_authority() {
194 let uri: Uri = "mailto:user@example.com".parse().unwrap();
196 let err = FetchTransport::new(uri).unwrap_err();
197 let msg = format!("{err}");
198 assert!(
201 msg.contains("authority") || msg.contains("scheme"),
202 "unexpected error: {msg}"
203 );
204 }
205
206 #[test]
207 fn fetch_transport_accepts_https_uri() {
208 let uri: Uri = "https://example.com:8443/base".parse().unwrap();
209 let t = FetchTransport::new(uri).expect("valid base URI");
210 assert_eq!(t.scheme, Scheme::HTTPS);
211 assert_eq!(t.authority.as_str(), "example.com:8443");
212 }
213
214 #[test]
215 fn rewrite_uri_replaces_scheme_and_authority_keeps_path() {
216 let req: http::Request<()> = http::Request::builder()
217 .uri("http://placeholder.invalid/foo/bar?x=1")
218 .body(())
219 .unwrap();
220 let scheme = Scheme::HTTPS;
221 let authority: Authority = "real.example:8443".parse().unwrap();
222 let rewritten = rewrite_uri(req, scheme, authority).unwrap();
223 let uri = rewritten.uri();
224 assert_eq!(uri.scheme_str(), Some("https"));
225 assert_eq!(
226 uri.authority().map(|a| a.as_str()),
227 Some("real.example:8443")
228 );
229 assert_eq!(uri.path(), "/foo/bar");
230 assert_eq!(uri.query(), Some("x=1"));
231 }
232
233 #[test]
234 fn rewrite_uri_defaults_path_to_root_when_missing() {
235 let req: http::Request<()> = http::Request::builder()
237 .uri("http://placeholder")
238 .body(())
239 .unwrap();
240 let scheme = Scheme::HTTP;
241 let authority: Authority = "real.example".parse().unwrap();
242 let rewritten = rewrite_uri(req, scheme, authority).unwrap();
243 assert_eq!(rewritten.uri().path(), "/");
244 }
245
246 #[test]
251 fn transports_implement_client_transport() {
252 fn assert_transport<T: ClientTransport>() {}
253 assert_transport::<FetcherTransport>();
254 assert_transport::<FetchTransport>();
255 }
256}