arpy_reqwasm/
http.rs

1//! HTTP Client.
2//!
3//! See [`Connection`] for an example.
4use arpy::{FnRemote, MimeType, RpcClient};
5use async_trait::async_trait;
6use js_sys::Uint8Array;
7use reqwasm::http;
8
9use crate::Error;
10
11/// A connection to the server.
12///
13/// # Example
14///
15/// ```
16#[doc = include_doc::function_body!("tests/doc.rs", http_client, [my_app, MyAdd])]
17/// ```
18#[derive(Clone)]
19pub struct Connection(String);
20
21impl Connection {
22    /// Constructor.
23    ///
24    /// `url` is the base url of the server, and will have [`MsgId::ID`]
25    /// appended for each RPC.
26    ///
27    /// [`MsgId::ID`]: arpy::protocol::MsgId::ID
28    pub fn new(url: &str) -> Self {
29        Self(url.to_string())
30    }
31}
32
33#[async_trait(?Send)]
34impl RpcClient for Connection {
35    type Error = Error;
36
37    async fn call<Args>(&self, args: Args) -> Result<Args::Output, Self::Error>
38    where
39        Args: FnRemote,
40    {
41        let content_type = MimeType::Cbor;
42        let mut body = Vec::new();
43        ciborium::ser::into_writer(&args, &mut body).unwrap();
44
45        let js_body = Uint8Array::new_with_length(body.len().try_into().unwrap());
46        js_body.copy_from(&body);
47
48        let result = http::Request::post(&format!("{}/{}", self.0, Args::ID))
49            .header(CONTENT_TYPE, content_type.as_str())
50            .header(ACCEPT, content_type.as_str())
51            .body(js_body)
52            .send()
53            .await
54            .map_err(Error::send)?;
55
56        if !result.ok() {
57            return Err(Error::Receive(format!(
58                "HTTP error code {}",
59                result.status()
60            )));
61        }
62
63        if let Some(result_type) = result.headers().get(CONTENT_TYPE) {
64            if result_type != content_type.as_str() {
65                return Err(Error::UnknownContentType(result_type));
66            }
67        }
68
69        let result_bytes = result.binary().await.map_err(Error::receive)?;
70        let result: Args::Output = ciborium::de::from_reader(result_bytes.as_slice())
71            .map_err(Error::deserialize_result)?;
72
73        Ok(result)
74    }
75}
76
77const ACCEPT: &str = "accept";
78const CONTENT_TYPE: &str = "content-type";