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