spin_sdk/http/
conversions.rs

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