signalrs_client_custom_auth/hub/
mod.rs

1//! Client-side hub
2
3pub mod arguments;
4pub mod error;
5mod functions;
6pub(crate) mod invocation;
7
8use self::{
9    error::{HubError, MalformedRequest},
10    functions::{Handler, HandlerWrapper, HubMethod},
11    invocation::HubInvocation,
12};
13use super::messages::ClientMessage;
14use crate::protocol::MessageType;
15use serde::Deserialize;
16use std::collections::HashMap;
17use tracing::*;
18
19/// Client-side hub
20///
21/// [`Hub`] can be called by the server. It currently supports only value-like arguments - ones that can be deserialized from a single message.
22/// There are also stream-like arguments - ones that server can stream to client asynchronously, but they are not supported yet.
23/// ```rust, no_run
24/// use signalrs_client::SignalRClient;
25/// use signalrs_client::hub::Hub;
26///
27/// #[tokio::main]
28/// async fn main() -> Result<(), Box<dyn std::error::Error>> {
29///     let hub = Hub::default().method("Send", print);    
30///
31///     let client = SignalRClient::builder("localhost")
32///         .use_port(8080)
33///         .use_hub("echo")
34///         .with_client_hub(hub)
35///         .build()
36///         .await?;
37/// # Ok(())
38/// }
39///
40/// // Hub methods need to be async
41/// async fn print(message: String) {
42///     println!("{message}");
43/// }
44/// ```
45#[derive(Default)]
46pub struct Hub {
47    methods: HashMap<String, Box<dyn HubMethod + Send + Sync + 'static>>,
48}
49
50impl Hub {
51    /// Embeds a new method in a hub under `name`.
52    ///
53    /// Any server calls to a method of `name` will be routed to function pointed to by `method`.
54    /// Only functions returning `()` and `async` are allowed.
55    /// Up to 13 arguments are supported. They will be extracted from server call.
56    /// In case extraction fails, error will be logged.
57    ///
58    /// All primitive arguments should be usable out of the box. In case custom one needs to be used see [`HubArgument`](signalrs_derive::HubArgument).
59    /// Value-like hub arguments need to implement [`Deserialize`](serde::Deserialize) as well.
60    ///
61    /// # Example
62    /// ```rust,no_run
63    /// use serde::Deserialize;
64    /// use signalrs_derive::HubArgument;
65    /// # use signalrs_client::hub::Hub;
66    /// # fn main() -> Result<(), Box<dyn std::error::Error>> {
67    /// #     let hub = Hub::default().method("Send", print);    
68    /// #     Ok(())
69    /// # }
70    /// # async fn print(_message: Data) {
71    /// #    // do nothing
72    /// # }
73    ///
74    /// #[derive(Deserialize, HubArgument)]
75    /// struct Data {
76    ///     f1: i32,
77    ///     f2: String
78    /// }
79    /// ```
80    pub fn method<M, Args>(mut self, name: impl ToString, method: M) -> Self
81    where
82        M: Handler<Args> + Send + Sync + Clone + 'static,
83        Args: Send + Sync + 'static,
84    {
85        if self
86            .methods
87            .insert(name.to_string(), Box::new(HandlerWrapper::<M, Args>::from(method)))
88            .is_some()
89        {
90            warn!("overwritten method {}", name.to_string())
91        }
92
93        self
94    }
95
96    pub(crate) fn call(&self, message: ClientMessage) -> Result<(), HubError> {
97        let RoutingData {
98            message_type,
99            target,
100        } = message
101            .deserialize()
102            .map_err(|error| -> MalformedRequest { error.into() })?;
103
104        match message_type {
105            MessageType::Invocation => self.invocation(target, message),
106            x => self.unsupported(x),
107        }
108    }
109
110    fn invocation(&self, target: Option<String>, message: ClientMessage) -> Result<(), HubError> {
111        let target = target.ok_or_else(|| HubError::Unprocessable {
112            message: "Target of invocation missing in request".into(),
113        })?;
114
115        let method = self
116            .methods
117            .get(&target)
118            .ok_or_else(|| HubError::Unprocessable {
119                message: format!("target {} not found", target),
120            })?;
121
122        method.call(HubInvocation::new(message)?)
123    }
124
125    fn unsupported(&self, message_type: MessageType) -> Result<(), HubError> {
126        Err(HubError::Unsupported {
127            message: format!("{message_type} not supported by client-side hub"),
128        })
129    }
130}
131
132#[derive(Deserialize)]
133struct RoutingData {
134    #[serde(rename = "type")]
135    message_type: MessageType,
136    target: Option<String>,
137}