1use std::{convert::identity, pin::Pin, str::FromStr, sync::Arc};
8
9use actix_web::{
10 body::BoxBody,
11 error::{self, ErrorBadRequest, ErrorNotAcceptable, ErrorUnsupportedMediaType},
12 http::header::{HeaderValue, ACCEPT, CONTENT_TYPE},
13 web::Bytes,
14 FromRequest, HttpRequest, HttpResponse, Responder,
15};
16use arpy::{FnRemote, MimeType};
17use arpy_server::FnRemoteBody;
18use futures::Future;
19use serde::Serialize;
20
21#[doc = include_doc::function_body!("tests/doc.rs", extractor_example, [my_handler, MyAdd])]
32pub struct ArpyRequest<T>(pub T);
36
37impl<Args: FnRemote> FromRequest for ArpyRequest<Args> {
38 type Error = error::Error;
39 type Future = Pin<Box<dyn Future<Output = Result<Self, Self::Error>>>>;
40
41 fn from_request(req: &HttpRequest, payload: &mut actix_web::dev::Payload) -> Self::Future {
42 let content_type = mime_type(req.headers().get(CONTENT_TYPE));
43 let bytes = Bytes::from_request(req, payload);
44
45 Box::pin(async move {
46 let body = bytes.await?;
47 let body = body.as_ref();
48
49 let args: Args = match content_type? {
50 MimeType::Cbor => ciborium::de::from_reader(body).map_err(ErrorBadRequest)?,
51 MimeType::Json => serde_json::from_slice(body).map_err(ErrorBadRequest)?,
52 MimeType::XwwwFormUrlencoded => {
53 serde_urlencoded::from_bytes(body).map_err(ErrorBadRequest)?
54 }
55 };
56
57 Ok(ArpyRequest(args))
58 })
59 }
60}
61
62pub struct ArpyResponse<T>(pub T);
71
72impl<T> Responder for ArpyResponse<T>
73where
74 T: Serialize,
75{
76 type Body = BoxBody;
77
78 fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
79 try_respond_to(self.0, req).map_or_else(|e| e.error_response(), identity)
80 }
81}
82
83fn try_respond_to<T>(response: T, req: &HttpRequest) -> Result<HttpResponse, error::Error>
84where
85 T: Serialize,
86{
87 let response_type = mime_type(req.headers().get(ACCEPT))?;
88
89 let body = match response_type {
90 MimeType::Cbor => {
91 let mut response_body = Vec::new();
92
93 ciborium::ser::into_writer(&response, &mut response_body).map_err(ErrorBadRequest)?;
94 BoxBody::new(response_body)
95 }
96 MimeType::Json => BoxBody::new(serde_json::to_vec(&response).map_err(ErrorBadRequest)?),
97 MimeType::XwwwFormUrlencoded => BoxBody::new(serde_urlencoded::to_string(&response)?),
98 };
99
100 Ok(HttpResponse::Ok()
101 .content_type(response_type.as_str())
102 .body(body))
103}
104
105#[doc = include_doc::function_body!("tests/doc.rs", router_example, [my_add, MyAdd])]
114pub async fn handler<F, Args>(f: Arc<F>, ArpyRequest(args): ArpyRequest<Args>) -> impl Responder
118where
119 F: FnRemoteBody<Args>,
120 Args: FnRemote,
121{
122 ArpyResponse(f.run(args).await)
123}
124
125fn mime_type(header_value: Option<&HeaderValue>) -> Result<MimeType, error::Error> {
126 if let Some(accept) = header_value {
127 let accept = accept.to_str().map_err(ErrorNotAcceptable)?;
128 MimeType::from_str(accept)
129 .map_err(|_| ErrorUnsupportedMediaType(format!("Unsupport mime type '{accept}'")))
130 } else {
131 Ok(MimeType::Cbor)
132 }
133}