graphql_ws_client/next/
connection.rs

1use std::future::Future;
2use std::pin::Pin;
3
4use crate::Error;
5
6/// Abstraction around a websocket connection.
7///
8/// Built in implementations are provided for `ws_stream_wasm` & `async_tungstenite`.
9///
10/// If users wish to add support for a new client they should implement this trait.
11pub trait Connection {
12    /// Receive the next message on this connection.
13    fn receive(&mut self) -> impl Future<Output = Option<Message>> + Send;
14
15    /// Send a message with on connection
16    fn send(&mut self, message: Message) -> impl Future<Output = Result<(), Error>> + Send;
17}
18
19/// A websocket message
20///
21/// Websocket client libraries usually provide their own version of this struct.
22/// The [Connection] trait for a given client should handle translation to & from this enum.
23pub enum Message {
24    /// A message containing the given text payload
25    Text(String),
26    /// A message that closes the connection with the given code & reason
27    Close {
28        /// The status code for this close message
29        code: Option<u16>,
30        /// Some text explaining the reason the connection is being closed
31        reason: Option<String>,
32    },
33    /// A ping
34    Ping,
35    /// A reply to a ping
36    Pong,
37}
38
39impl Message {
40    pub(crate) fn deserialize<T>(self) -> Result<T, Error>
41    where
42        T: serde::de::DeserializeOwned,
43    {
44        let Message::Text(text) = self else {
45            panic!("Don't call deserialize on non-text messages");
46        };
47
48        serde_json::from_str(&text).map_err(|error| Error::Decode(error.to_string()))
49    }
50
51    pub(crate) fn init(payload: Option<serde_json::Value>) -> Self {
52        Self::Text(
53            serde_json::to_string(&crate::protocol::ConnectionInit::new(payload))
54                .expect("payload is already serialized so this shouldn't fail"),
55        )
56    }
57
58    pub(crate) fn graphql_pong() -> Self {
59        Self::Text(serde_json::to_string(&crate::protocol::Message::Pong::<()>).unwrap())
60    }
61
62    pub(crate) fn graphql_ping() -> Self {
63        Self::Text(serde_json::to_string(&crate::protocol::Message::Ping::<()>).unwrap())
64    }
65
66    pub(crate) fn complete(id: usize) -> Self {
67        Self::Text(
68            serde_json::to_string(&crate::protocol::Message::Complete::<()> { id: id.to_string() })
69                .unwrap(),
70        )
71    }
72}
73
74/// An object safe wrapper around the Connection trait, allowing us
75/// to use it dynamically
76pub(crate) trait ObjectSafeConnection: Send {
77    fn receive(&mut self) -> Pin<Box<dyn Future<Output = Option<Message>> + Send + '_>>;
78
79    fn send(
80        &mut self,
81        message: Message,
82    ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + '_>>;
83}
84
85impl<T: Connection + Sized + Send> ObjectSafeConnection for T {
86    fn receive(&mut self) -> Pin<Box<dyn Future<Output = Option<Message>> + Send + '_>> {
87        Box::pin(Connection::receive(self))
88    }
89
90    fn send(
91        &mut self,
92        message: Message,
93    ) -> Pin<Box<dyn Future<Output = Result<(), Error>> + Send + '_>> {
94        Box::pin(Connection::send(self, message))
95    }
96}