twilight_gateway/
session.rs

1//! Active gateway session details.
2
3use serde::{Deserialize, Serialize};
4use std::mem;
5
6/// Gateway session information for a shard's active connection.
7///
8/// A session is a stateful identifier on Discord's end for running a [shard].
9/// It is used for maintaining an authenticated Websocket connection based on
10/// an [identifier]. While a session is only connected to one shard, one shard
11/// can have more than one session: if a shard shuts down its connection and
12/// starts a new session, then the previous session will be kept alive for a
13/// short time.
14///
15/// # Reusing sessions
16///
17/// Sessions are able to be reused across connections to Discord. If an
18/// application's process needs to be restarted, then this session
19/// information—which can be (de)serialized via serde—can be stored, the
20/// application restarted, and then used again via [`ConfigBuilder::session`].
21///
22/// If the delay between disconnecting from the gateway and reconnecting isn't
23/// too long and Discord hasn't invalidated the session, then the session will
24/// be reused by Discord. As a result, any events that were "missed" while
25/// restarting and reconnecting will be played back, meaning the application
26/// won't have missed any events. If the delay has been too long, then a new
27/// session will be initialized, resulting in those events being missed.
28///
29/// [`ConfigBuilder::session`]: crate::ConfigBuilder::session
30/// [identifier]: Self::id
31/// [shard]: crate::Shard
32#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
33pub struct Session {
34    /// ID of the gateway session.
35    id: Box<str>,
36    /// Sequence of the most recently received gateway event.
37    ///
38    /// The first sequence of a session is always 1.
39    sequence: u64,
40}
41
42impl Session {
43    /// Create new configuration for resuming a gateway session.
44    ///
45    /// Can be provided to [`ConfigBuilder::session`].
46    ///
47    /// [`ConfigBuilder::session`]: crate::ConfigBuilder::session
48    pub fn new(sequence: u64, session_id: String) -> Self {
49        Self {
50            sequence,
51            id: session_id.into_boxed_str(),
52        }
53    }
54
55    /// ID of the session being resumed.
56    ///
57    /// The ID of the session is different from the [ID of the shard]; shards are
58    /// identified by an index, and when authenticated with the gateway the shard
59    /// is given a unique identifier for the gateway session.
60    ///
61    /// Session IDs are obtained by shards via sending an [`Identify`] command
62    /// with the shard's authentication details, and in return the session ID is
63    /// provided via the [`Ready`] event.
64    ///
65    /// [`Identify`]: twilight_model::gateway::payload::outgoing::Identify
66    /// [`Ready`]: twilight_model::gateway::payload::incoming::Ready
67    /// [ID of the shard]: crate::ShardId
68    pub const fn id(&self) -> &str {
69        &self.id
70    }
71
72    /// Current sequence of the connection.
73    ///
74    /// Number of the events that have been received during this session. A
75    /// larger number typically correlates that the shard has been connected
76    /// with this session for a longer time, while a smaller number typically
77    /// correlates to meaning that it's been connected with this session for a
78    /// shorter duration of time.
79    ///
80    /// As a shard is connected to the gateway and receives events this sequence
81    /// will be updated in real time when obtaining the [session of a shard].
82    ///
83    /// [session of a shard]: crate::Shard::session
84    pub const fn sequence(&self) -> u64 {
85        self.sequence
86    }
87
88    /// Set the sequence, returning the previous sequence.
89    pub(crate) fn set_sequence(&mut self, sequence: u64) -> u64 {
90        mem::replace(&mut self.sequence, sequence)
91    }
92}
93
94#[cfg(test)]
95mod tests {
96    use super::Session;
97    use serde::{Deserialize, Serialize};
98    use serde_test::Token;
99    use static_assertions::assert_impl_all;
100    use std::fmt::Debug;
101
102    assert_impl_all!(
103        Session: Clone,
104        Debug,
105        Deserialize<'static>,
106        Eq,
107        PartialEq,
108        Send,
109        Serialize,
110        Sync
111    );
112
113    /// Test that sessions deserialize and serialize the same way.
114    #[test]
115    fn serde() {
116        const SEQUENCE: u64 = 56_132;
117        const SESSION_ID: &str = "thisisanid";
118
119        let value = Session::new(SEQUENCE, SESSION_ID.to_owned());
120
121        serde_test::assert_tokens(
122            &value,
123            &[
124                Token::Struct {
125                    name: "Session",
126                    len: 2,
127                },
128                Token::Str("id"),
129                Token::Str(SESSION_ID),
130                Token::Str("sequence"),
131                Token::U64(SEQUENCE),
132                Token::StructEnd,
133            ],
134        );
135    }
136
137    /// Test that session getters return the provided values.
138    #[test]
139    fn session() {
140        const SESSIONS: [(u64, &str); 2] = [(1, "a"), (2, "b")];
141
142        for (sequence, session_id) in SESSIONS {
143            let session = Session::new(sequence, session_id.to_owned());
144            assert_eq!(session.sequence(), sequence);
145            assert_eq!(session.id(), session_id);
146        }
147    }
148
149    /// Test that setting the sequence actually updates the sequence and returns
150    /// the previous sequence.
151    #[test]
152    fn set_sequence() {
153        const SEQUENCE_INITIAL: u64 = 1;
154        const SEQUENCE_NEXT: u64 = SEQUENCE_INITIAL + 1;
155        const SEQUENCE_SKIPPED: u64 = SEQUENCE_NEXT + 3;
156
157        let mut session = Session::new(SEQUENCE_INITIAL, String::new());
158        let old = session.set_sequence(SEQUENCE_NEXT);
159        assert_eq!(old, SEQUENCE_INITIAL);
160
161        // although we don't expect to skip sequences the setter should still
162        // handle them as usual
163        let skipped_old = session.set_sequence(SEQUENCE_SKIPPED);
164        assert_eq!(skipped_old, SEQUENCE_NEXT);
165    }
166}