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}