spin_sdk/http/
conversions.rs

1use std::collections::HashMap;
2
3use async_trait::async_trait;
4
5use wasi::io::streams;
6
7use super::{
8    Headers, IncomingRequest, IncomingResponse, Method, OutgoingRequest, OutgoingResponse,
9    RequestBuilder,
10};
11#[cfg(feature = "json")]
12use super::{Json, JsonBodyError};
13
14use super::{responses, NonUtf8BodyError, Request, Response};
15
16impl TryFrom<Response> for OutgoingResponse {
17    type Error = anyhow::Error;
18
19    fn try_from(response: Response) -> anyhow::Result<Self> {
20        let headers = response
21            .headers
22            .into_iter()
23            .map(|(k, v)| (k, v.into_bytes()))
24            .collect::<Vec<_>>();
25        let res = OutgoingResponse::new(Headers::from_list(&headers)?);
26        res.set_status_code(response.status)
27            .map_err(|()| anyhow::anyhow!("error setting status code to {}", response.status))?;
28        Ok(res)
29    }
30}
31
32/// A trait for trying to convert from an `IncomingRequest` to the implementing type
33#[async_trait]
34pub trait TryFromIncomingRequest {
35    /// The error if conversion fails
36    type Error;
37
38    /// Try to turn the `IncomingRequest` into the implementing type
39    async fn try_from_incoming_request(value: IncomingRequest) -> Result<Self, Self::Error>
40    where
41        Self: Sized;
42}
43
44#[async_trait]
45impl TryFromIncomingRequest for IncomingRequest {
46    type Error = std::convert::Infallible;
47    async fn try_from_incoming_request(request: IncomingRequest) -> Result<Self, Self::Error> {
48        Ok(request)
49    }
50}
51
52#[async_trait]
53impl<R> TryFromIncomingRequest for R
54where
55    R: TryNonRequestFromRequest,
56{
57    type Error = IncomingRequestError<R::Error>;
58
59    async fn try_from_incoming_request(request: IncomingRequest) -> Result<Self, Self::Error> {
60        let req = Request::try_from_incoming_request(request)
61            .await
62            .map_err(convert_error)?;
63        R::try_from_request(req).map_err(IncomingRequestError::ConversionError)
64    }
65}
66
67#[async_trait]
68impl TryFromIncomingRequest for Request {
69    type Error = IncomingRequestError;
70
71    async fn try_from_incoming_request(request: IncomingRequest) -> Result<Self, Self::Error> {
72        Ok(Request::builder()
73            .method(request.method())
74            .uri(request.uri())
75            .headers(request.headers())
76            .body(request.into_body().await.map_err(|e| {
77                IncomingRequestError::BodyConversionError(anyhow::anyhow!(
78                    "{}",
79                    e.to_debug_string()
80                ))
81            })?)
82            .build())
83    }
84}
85
86#[derive(Debug, thiserror::Error)]
87/// An error converting an `IncomingRequest`
88pub enum IncomingRequestError<E = std::convert::Infallible> {
89    /// There was an error converting the body to an `Option<Vec<u8>>k`
90    #[error(transparent)]
91    BodyConversionError(anyhow::Error),
92    /// There was an error converting the `Request` into the requested type
93    #[error(transparent)]
94    ConversionError(E),
95}
96
97/// Helper for converting `IncomingRequestError`s that cannot fail due to conversion errors
98/// into ones that can.
99fn convert_error<E>(
100    error: IncomingRequestError<std::convert::Infallible>,
101) -> IncomingRequestError<E> {
102    match error {
103        IncomingRequestError::BodyConversionError(e) => {
104            IncomingRequestError::BodyConversionError(e)
105        }
106        IncomingRequestError::ConversionError(_) => unreachable!(),
107    }
108}
109
110impl<E: IntoResponse> IntoResponse for IncomingRequestError<E> {
111    fn into_response(self) -> Response {
112        match self {
113            IncomingRequestError::BodyConversionError(e) => e.into_response(),
114            IncomingRequestError::ConversionError(e) => e.into_response(),
115        }
116    }
117}
118
119/// A trait for any type that can be constructor from a `Request`
120pub trait TryFromRequest {
121    /// The error if the conversion fails
122    type Error;
123    /// Try to turn the request into the type
124    fn try_from_request(req: Request) -> Result<Self, Self::Error>
125    where
126        Self: Sized;
127}
128
129impl TryFromRequest for Request {
130    type Error = std::convert::Infallible;
131
132    fn try_from_request(req: Request) -> Result<Self, Self::Error>
133    where
134        Self: Sized,
135    {
136        Ok(req)
137    }
138}
139
140impl<R: TryNonRequestFromRequest> TryFromRequest for R {
141    type Error = R::Error;
142
143    fn try_from_request(req: Request) -> Result<Self, Self::Error>
144    where
145        Self: Sized,
146    {
147        TryNonRequestFromRequest::try_from_request(req)
148    }
149}
150
151/// A hack that allows us to do blanket impls for `T where T: TryFromRequest` for all types
152/// `T` *except* for `Request`.
153///
154/// This is useful in `wasi_http` where we want to implement `TryFromIncomingRequest` for all types that impl
155/// `TryFromRequest` with the exception of `Request` itself. This allows that implementation to first convert
156/// the `IncomingRequest` to a `Request` and then using this trait convert from `Request` to the given type.
157pub trait TryNonRequestFromRequest {
158    /// The error if the conversion fails
159    type Error;
160    /// Try to turn the request into the type
161    fn try_from_request(req: Request) -> Result<Self, Self::Error>
162    where
163        Self: Sized;
164}
165
166impl<B: TryFromBody> TryNonRequestFromRequest for hyperium::Request<B> {
167    type Error = B::Error;
168    fn try_from_request(req: Request) -> Result<Self, Self::Error> {
169        let mut builder = hyperium::Request::builder()
170            .uri(req.uri())
171            .method(req.method);
172        for (n, v) in req.headers {
173            builder = builder.header(n, v.into_bytes());
174        }
175        Ok(builder.body(B::try_from_body(req.body)?).unwrap())
176    }
177}
178
179impl From<super::Method> for hyperium::Method {
180    fn from(method: super::Method) -> Self {
181        match method {
182            super::Method::Get => hyperium::Method::GET,
183            super::Method::Post => hyperium::Method::POST,
184            super::Method::Put => hyperium::Method::PUT,
185            super::Method::Delete => hyperium::Method::DELETE,
186            super::Method::Patch => hyperium::Method::PATCH,
187            super::Method::Head => hyperium::Method::HEAD,
188            super::Method::Options => hyperium::Method::OPTIONS,
189            super::Method::Connect => hyperium::Method::CONNECT,
190            super::Method::Trace => hyperium::Method::TRACE,
191            super::Method::Other(o) => hyperium::Method::from_bytes(o.as_bytes()).expect("TODO"),
192        }
193    }
194}
195impl From<hyperium::Method> for super::Method {
196    fn from(method: hyperium::Method) -> Self {
197        match method {
198            hyperium::Method::GET => super::Method::Get,
199            hyperium::Method::POST => super::Method::Post,
200            hyperium::Method::PUT => super::Method::Put,
201            hyperium::Method::DELETE => super::Method::Delete,
202            hyperium::Method::PATCH => super::Method::Patch,
203            hyperium::Method::HEAD => super::Method::Head,
204            hyperium::Method::OPTIONS => super::Method::Options,
205            hyperium::Method::CONNECT => super::Method::Connect,
206            hyperium::Method::TRACE => super::Method::Trace,
207            m => super::Method::Other(m.as_str().into()),
208        }
209    }
210}
211
212/// A trait for any type that can be turned into a `Response`
213pub trait IntoResponse {
214    /// Turn `self` into a `Response`
215    fn into_response(self) -> Response;
216}
217
218impl IntoResponse for Response {
219    fn into_response(self) -> Response {
220        self
221    }
222}
223
224impl<B> IntoResponse for hyperium::Response<B>
225where
226    B: IntoBody,
227{
228    fn into_response(self) -> Response {
229        Response::builder()
230            .status(self.status().as_u16())
231            .headers(self.headers())
232            .body(self.into_body())
233            .build()
234    }
235}
236
237impl<R: IntoResponse, E: IntoResponse> IntoResponse for std::result::Result<R, E> {
238    fn into_response(self) -> Response {
239        match self {
240            Ok(r) => r.into_response(),
241            Err(e) => e.into_response(),
242        }
243    }
244}
245
246impl IntoResponse for anyhow::Error {
247    fn into_response(self) -> Response {
248        let body = self.to_string();
249        eprintln!("Handler returned an error: {}", body);
250        let mut source = self.source();
251        while let Some(s) = source {
252            eprintln!("  caused by: {}", s);
253            source = s.source();
254        }
255        Response {
256            status: 500,
257            headers: Default::default(),
258            body: body.as_bytes().to_vec(),
259        }
260    }
261}
262
263impl IntoResponse for Box<dyn std::error::Error> {
264    fn into_response(self) -> Response {
265        let body = self.to_string();
266        eprintln!("Handler returned an error: {}", body);
267        let mut source = self.source();
268        while let Some(s) = source {
269            eprintln!("  caused by: {}", s);
270            source = s.source();
271        }
272        Response {
273            status: 500,
274            headers: Default::default(),
275            body: body.as_bytes().to_vec(),
276        }
277    }
278}
279
280#[cfg(feature = "json")]
281impl IntoResponse for super::JsonBodyError {
282    fn into_response(self) -> Response {
283        responses::bad_request(Some(format!("invalid JSON body: {}", self.0)))
284    }
285}
286
287impl IntoResponse for NonUtf8BodyError {
288    fn into_response(self) -> Response {
289        responses::bad_request(Some(
290            "expected body to be a utf8 string but wasn't".to_owned(),
291        ))
292    }
293}
294
295impl IntoResponse for std::convert::Infallible {
296    fn into_response(self) -> Response {
297        unreachable!()
298    }
299}
300
301/// A trait for any type that can be turned into a `Response` status code
302pub trait IntoStatusCode {
303    /// Turn `self` into a status code
304    fn into_status_code(self) -> u16;
305}
306
307impl IntoStatusCode for u16 {
308    fn into_status_code(self) -> u16 {
309        self
310    }
311}
312
313impl IntoStatusCode for hyperium::StatusCode {
314    fn into_status_code(self) -> u16 {
315        self.as_u16()
316    }
317}
318
319/// A trait for any type that can be turned into `Response` headers
320pub trait IntoHeaders {
321    /// Turn `self` into `Response` headers
322    fn into_headers(self) -> Vec<(String, Vec<u8>)>;
323}
324
325impl IntoHeaders for Vec<(String, String)> {
326    fn into_headers(self) -> Vec<(String, Vec<u8>)> {
327        self.into_iter().map(|(k, v)| (k, v.into_bytes())).collect()
328    }
329}
330
331impl IntoHeaders for Vec<(String, Vec<u8>)> {
332    fn into_headers(self) -> Vec<(String, Vec<u8>)> {
333        self
334    }
335}
336
337impl IntoHeaders for HashMap<String, Vec<String>> {
338    fn into_headers(self) -> Vec<(String, Vec<u8>)> {
339        self.into_iter()
340            .flat_map(|(k, values)| values.into_iter().map(move |v| (k.clone(), v.into_bytes())))
341            .collect()
342    }
343}
344
345impl IntoHeaders for HashMap<String, String> {
346    fn into_headers(self) -> Vec<(String, Vec<u8>)> {
347        self.into_iter().map(|(k, v)| (k, v.into_bytes())).collect()
348    }
349}
350
351impl IntoHeaders for HashMap<String, Vec<u8>> {
352    fn into_headers(self) -> Vec<(String, Vec<u8>)> {
353        self.into_iter().collect()
354    }
355}
356
357impl IntoHeaders for &hyperium::HeaderMap {
358    fn into_headers(self) -> Vec<(String, Vec<u8>)> {
359        self.iter()
360            .map(|(k, v)| (k.as_str().to_owned(), v.as_bytes().to_owned()))
361            .collect()
362    }
363}
364
365impl IntoHeaders for Headers {
366    fn into_headers(self) -> Vec<(String, Vec<u8>)> {
367        self.entries().into_headers()
368    }
369}
370
371/// A trait for any type that can be turned into a `Response` body
372pub trait IntoBody {
373    /// Turn `self` into a `Response` body
374    fn into_body(self) -> Vec<u8>;
375}
376
377impl<T: IntoBody> IntoBody for Option<T> {
378    fn into_body(self) -> Vec<u8> {
379        self.map(|b| IntoBody::into_body(b)).unwrap_or_default()
380    }
381}
382
383impl IntoBody for Vec<u8> {
384    fn into_body(self) -> Vec<u8> {
385        self
386    }
387}
388
389impl IntoBody for bytes::Bytes {
390    fn into_body(self) -> Vec<u8> {
391        self.to_vec()
392    }
393}
394
395impl IntoBody for () {
396    fn into_body(self) -> Vec<u8> {
397        Default::default()
398    }
399}
400
401impl IntoBody for &str {
402    fn into_body(self) -> Vec<u8> {
403        self.to_owned().into_bytes()
404    }
405}
406
407impl IntoBody for String {
408    fn into_body(self) -> Vec<u8> {
409        self.to_owned().into_bytes()
410    }
411}
412
413/// A trait for converting from a body or failing
414pub trait TryFromBody {
415    /// The error encountered if conversion fails
416    type Error: IntoResponse;
417    /// Convert from a body to `Self` or fail
418    fn try_from_body(body: Vec<u8>) -> Result<Self, Self::Error>
419    where
420        Self: Sized;
421}
422
423impl<T: TryFromBody> TryFromBody for Option<T> {
424    type Error = T::Error;
425
426    fn try_from_body(body: Vec<u8>) -> Result<Self, Self::Error>
427    where
428        Self: Sized,
429    {
430        Ok(Some(TryFromBody::try_from_body(body)?))
431    }
432}
433
434impl<T: FromBody> TryFromBody for T {
435    type Error = std::convert::Infallible;
436
437    fn try_from_body(body: Vec<u8>) -> Result<Self, Self::Error>
438    where
439        Self: Sized,
440    {
441        Ok(FromBody::from_body(body))
442    }
443}
444
445impl TryFromBody for String {
446    type Error = NonUtf8BodyError;
447
448    fn try_from_body(body: Vec<u8>) -> Result<Self, Self::Error>
449    where
450        Self: Sized,
451    {
452        String::from_utf8(body).map_err(|_| NonUtf8BodyError)
453    }
454}
455
456#[cfg(feature = "json")]
457impl<T: serde::de::DeserializeOwned> TryFromBody for Json<T> {
458    type Error = JsonBodyError;
459    fn try_from_body(body: Vec<u8>) -> Result<Self, Self::Error> {
460        Ok(Json(serde_json::from_slice(&body).map_err(JsonBodyError)?))
461    }
462}
463
464/// A trait from converting from a body
465pub trait FromBody {
466    /// Convert from a body into the type
467    fn from_body(body: Vec<u8>) -> Self;
468}
469
470impl FromBody for Vec<u8> {
471    fn from_body(body: Vec<u8>) -> Self {
472        body
473    }
474}
475
476impl FromBody for () {
477    fn from_body(_body: Vec<u8>) -> Self {}
478}
479
480impl FromBody for bytes::Bytes {
481    fn from_body(body: Vec<u8>) -> Self {
482        Into::into(body)
483    }
484}
485
486/// A trait for any type that can be turned into a `Response` body or fail
487pub trait TryIntoBody {
488    /// The type of error if the conversion fails
489    type Error;
490    /// Turn `self` into an Error
491    fn try_into_body(self) -> Result<Vec<u8>, Self::Error>;
492}
493
494impl<B> TryIntoBody for B
495where
496    B: IntoBody,
497{
498    type Error = std::convert::Infallible;
499
500    fn try_into_body(self) -> Result<Vec<u8>, Self::Error> {
501        Ok(self.into_body())
502    }
503}
504
505#[cfg(feature = "json")]
506impl<T: serde::Serialize> TryIntoBody for Json<T> {
507    type Error = JsonBodyError;
508
509    fn try_into_body(self) -> Result<Vec<u8>, Self::Error> {
510        serde_json::to_vec(&self.0).map_err(JsonBodyError)
511    }
512}
513
514/// A trait for converting a type into an `OutgoingRequest`
515pub trait TryIntoOutgoingRequest {
516    /// The error if the conversion fails
517    type Error;
518
519    /// Turn the type into an `OutgoingRequest`
520    ///
521    /// If the implementor can be sure that the `OutgoingRequest::write` has not been called they
522    /// can return a buffer as the second element of the returned tuple and `send` will send
523    /// that as the request body.
524    fn try_into_outgoing_request(self) -> Result<(OutgoingRequest, Option<Vec<u8>>), Self::Error>;
525}
526
527impl TryIntoOutgoingRequest for OutgoingRequest {
528    type Error = std::convert::Infallible;
529
530    fn try_into_outgoing_request(self) -> Result<(OutgoingRequest, Option<Vec<u8>>), Self::Error> {
531        Ok((self, None))
532    }
533}
534
535impl TryIntoOutgoingRequest for Request {
536    type Error = anyhow::Error;
537
538    fn try_into_outgoing_request(self) -> Result<(OutgoingRequest, Option<Vec<u8>>), Self::Error> {
539        let headers = self
540            .headers()
541            .map(|(k, v)| (k.to_owned(), v.as_bytes().to_owned()))
542            .collect::<Vec<_>>();
543        let request = OutgoingRequest::new(Headers::from_list(&headers)?);
544        request
545            .set_method(self.method())
546            .map_err(|()| anyhow::anyhow!("error setting method to {}", self.method()))?;
547        request
548            .set_path_with_query(self.path_and_query())
549            .map_err(|()| anyhow::anyhow!("error setting path to {:?}", self.path_and_query()))?;
550        request
551            .set_scheme(Some(if self.is_https() {
552                &super::Scheme::Https
553            } else {
554                &super::Scheme::Http
555            }))
556            // According to the documentation, `Request::set_scheme` can only fail due to a malformed
557            // `Scheme::Other` payload, but we never pass `Scheme::Other` above, hence the `unwrap`.
558            .unwrap();
559        let authority = self
560            .authority()
561            // `wasi-http` requires an authority for outgoing requests, so we always supply one:
562            .or_else(|| Some(if self.is_https() { ":443" } else { ":80" }));
563        request
564            .set_authority(authority)
565            .map_err(|()| anyhow::anyhow!("error setting authority to {authority:?}"))?;
566        Ok((request, Some(self.into_body())))
567    }
568}
569
570impl TryIntoOutgoingRequest for RequestBuilder {
571    type Error = anyhow::Error;
572
573    fn try_into_outgoing_request(
574        mut self,
575    ) -> Result<(OutgoingRequest, Option<Vec<u8>>), Self::Error> {
576        self.build().try_into_outgoing_request()
577    }
578}
579
580impl<B> TryIntoOutgoingRequest for hyperium::Request<B>
581where
582    B: TryIntoBody,
583    B::Error: std::error::Error + Send + Sync + 'static,
584{
585    type Error = anyhow::Error;
586    fn try_into_outgoing_request(self) -> Result<(OutgoingRequest, Option<Vec<u8>>), Self::Error> {
587        let headers = self
588            .headers()
589            .into_iter()
590            .map(|(n, v)| (n.as_str().to_owned(), v.as_bytes().to_owned()))
591            .collect::<Vec<_>>();
592        let request = OutgoingRequest::new(Headers::from_list(&headers)?);
593        request
594            .set_method(&self.method().clone().into())
595            .map_err(|()| {
596                anyhow::anyhow!(
597                    "error setting method to {}",
598                    Method::from(self.method().clone())
599                )
600            })?;
601        request
602            .set_path_with_query(self.uri().path_and_query().map(|p| p.as_str()))
603            .map_err(|()| {
604                anyhow::anyhow!("error setting path to {:?}", self.uri().path_and_query())
605            })?;
606        let scheme = self.uri().scheme().map(|s| match s.as_str() {
607            "http" => super::Scheme::Http,
608            "https" => super::Scheme::Https,
609            s => super::Scheme::Other(s.to_owned()),
610        });
611        request
612            .set_scheme(scheme.as_ref())
613            .map_err(|()| anyhow::anyhow!("error setting scheme to {scheme:?}"))?;
614        request
615            .set_authority(self.uri().authority().map(|a| a.as_str()))
616            .map_err(|()| {
617                anyhow::anyhow!("error setting authority to {:?}", self.uri().authority())
618            })?;
619        let buffer = TryIntoBody::try_into_body(self.into_body())?;
620        Ok((request, Some(buffer)))
621    }
622}
623
624/// A trait for converting from an `IncomingRequest`
625#[async_trait]
626pub trait TryFromIncomingResponse {
627    /// The error if conversion fails
628    type Error;
629    /// Turn the `IncomingResponse` into the type
630    async fn try_from_incoming_response(resp: IncomingResponse) -> Result<Self, Self::Error>
631    where
632        Self: Sized;
633}
634
635#[async_trait]
636impl TryFromIncomingResponse for IncomingResponse {
637    type Error = std::convert::Infallible;
638    async fn try_from_incoming_response(resp: IncomingResponse) -> Result<Self, Self::Error> {
639        Ok(resp)
640    }
641}
642
643#[async_trait]
644impl TryFromIncomingResponse for Response {
645    type Error = streams::Error;
646    async fn try_from_incoming_response(resp: IncomingResponse) -> Result<Self, Self::Error> {
647        Ok(Response::builder()
648            .status(resp.status())
649            .headers(resp.headers())
650            .body(resp.into_body().await?)
651            .build())
652    }
653}
654
655#[async_trait]
656impl<B: TryFromBody> TryFromIncomingResponse for hyperium::Response<B> {
657    type Error = B::Error;
658    async fn try_from_incoming_response(resp: IncomingResponse) -> Result<Self, Self::Error> {
659        let mut builder = hyperium::Response::builder().status(resp.status());
660        for (n, v) in resp.headers().entries() {
661            builder = builder.header(n, v);
662        }
663        let body = resp.into_body().await.expect("TODO");
664        Ok(builder.body(B::try_from_body(body)?).unwrap())
665    }
666}
667
668/// Turn a type into a `Request`
669pub trait TryIntoRequest {
670    /// The error if the conversion fails
671    type Error;
672
673    /// Turn `self` into a `Request`
674    fn try_into_request(self) -> Result<Request, Self::Error>;
675}
676
677impl TryIntoRequest for Request {
678    type Error = std::convert::Infallible;
679
680    fn try_into_request(self) -> Result<Request, Self::Error> {
681        Ok(self)
682    }
683}
684
685impl<B: TryIntoBody> TryIntoRequest for hyperium::Request<B> {
686    type Error = B::Error;
687    fn try_into_request(self) -> Result<Request, Self::Error> {
688        Ok(Request::builder()
689            .method(self.method().clone().into())
690            .uri(self.uri().to_string())
691            .headers(self.headers())
692            .body(B::try_into_body(self.into_body())?)
693            .build())
694    }
695}