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