twilight_model/gateway/event/
gateway.rs

1use super::{
2    super::OpCode, DispatchEvent, DispatchEventWithTypeDeserializer, Event, EventConversionError,
3};
4use crate::gateway::payload::incoming::Hello;
5use serde::{
6    de::{
7        value::U8Deserializer, DeserializeSeed, Deserializer, Error as DeError, IgnoredAny,
8        IntoDeserializer, MapAccess, Unexpected, Visitor,
9    },
10    ser::{SerializeStruct, Serializer},
11    Deserialize, Serialize,
12};
13use std::{
14    borrow::Cow,
15    fmt::{Formatter, Result as FmtResult},
16    str::FromStr,
17};
18
19/// An event from the gateway, which can either be a dispatch event with
20/// stateful updates or a heartbeat, hello, etc. that a shard needs to operate.
21#[allow(clippy::large_enum_variant)]
22#[derive(Clone, Debug)]
23pub enum GatewayEvent {
24    Dispatch(u64, DispatchEvent),
25    Heartbeat,
26    HeartbeatAck,
27    Hello(Hello),
28    InvalidateSession(bool),
29    Reconnect,
30}
31
32impl TryFrom<Event> for GatewayEvent {
33    type Error = EventConversionError;
34
35    fn try_from(event: Event) -> Result<Self, Self::Error> {
36        Ok(match event {
37            Event::GatewayHeartbeat => Self::Heartbeat,
38            Event::GatewayHeartbeatAck => Self::HeartbeatAck,
39            Event::GatewayHello(v) => Self::Hello(v),
40            Event::GatewayInvalidateSession(v) => Self::InvalidateSession(v),
41            Event::GatewayReconnect => Self::Reconnect,
42
43            _ => return Err(EventConversionError::new(event)),
44        })
45    }
46}
47
48#[derive(Clone, Copy, Debug, Deserialize, PartialEq)]
49#[serde(field_identifier, rename_all = "lowercase")]
50enum Field {
51    D,
52    Op,
53    S,
54    T,
55}
56
57/// Deserialize into a [`GatewayEvent`] by knowing its dispatch event type and
58/// opcode.
59#[derive(Debug)]
60pub struct GatewayEventDeserializer<'a> {
61    event_type: Option<Cow<'a, str>>,
62    op: u8,
63    sequence: Option<u64>,
64}
65
66impl<'a> GatewayEventDeserializer<'a> {
67    /// Create a new gateway event deserializer when you already know the opcode
68    /// and dispatch event type.
69    pub fn new(op: u8, event_type: Option<&'a str>) -> Self {
70        Self {
71            event_type: event_type.map(Into::into),
72            op,
73            sequence: None,
74        }
75    }
76
77    /// Create a gateway event deserializer by scanning the JSON payload for its
78    /// opcode and dispatch event type.
79    pub fn from_json(input: &'a str) -> Option<Self> {
80        let op = Self::find_opcode(input)?;
81        let event_type = Self::find_event_type(input).map(Into::into);
82        let sequence = Self::find_sequence(input);
83
84        Some(Self {
85            event_type,
86            op,
87            sequence,
88        })
89    }
90
91    /// Create a deserializer with an owned event type.
92    ///
93    /// This is necessary when using a mutable deserialization library such as
94    /// `simd-json`.
95    pub fn into_owned(self) -> GatewayEventDeserializer<'static> {
96        GatewayEventDeserializer {
97            event_type: self
98                .event_type
99                .map(|event_type| Cow::Owned(event_type.into_owned())),
100            op: self.op,
101            sequence: self.sequence,
102        }
103    }
104
105    /// Consume the deserializer, returning its components.
106    #[allow(clippy::missing_const_for_fn)]
107    pub fn into_parts(self) -> (u8, Option<u64>, Option<Cow<'a, str>>) {
108        (self.op, self.sequence, self.event_type)
109    }
110
111    /// Dispatch event type of the payload.
112    pub fn event_type(&self) -> Option<&str> {
113        self.event_type.as_deref()
114    }
115
116    /// Opcode of the payload.
117    pub const fn op(&self) -> u8 {
118        self.op
119    }
120
121    /// Sequence of the payload.
122    ///
123    /// May only be available if the deserializer was created via
124    /// [`from_json`][`Self::from_json`]
125    pub const fn sequence(&self) -> Option<u64> {
126        self.sequence
127    }
128
129    fn find_event_type(input: &'a str) -> Option<&'a str> {
130        // We're going to search for the event type key from the start. Discord
131        // always puts it at the front before the D key from some testing of
132        // several hundred payloads.
133        //
134        // If we find it, add 4, since that's the length of what we're searching
135        // for.
136        let from = input.find(r#""t":"#)? + 4;
137
138        // Now let's find where the value starts, which may be a string or null.
139        // Or maybe something else. If it's anything but a string, then there's
140        // no event type.
141        let start = input.get(from..)?.find(|c: char| !c.is_whitespace())? + from + 1;
142
143        // Check if the character just before the cursor is '"'.
144        if input.as_bytes().get(start - 1).copied()? != b'"' {
145            return None;
146        }
147
148        let to = input.get(start..)?.find('"')?;
149
150        input.get(start..start + to)
151    }
152
153    fn find_opcode(input: &'a str) -> Option<u8> {
154        Self::find_integer(input, r#""op":"#)
155    }
156
157    fn find_sequence(input: &'a str) -> Option<u64> {
158        Self::find_integer(input, r#""s":"#)
159    }
160
161    fn find_integer<T: FromStr>(input: &'a str, key: &str) -> Option<T> {
162        // Find the op key's position and then search for where the first
163        // character that's not base 10 is. This'll give us the bytes with the
164        // op which can be parsed.
165        //
166        // Add 5 at the end since that's the length of what we're finding.
167        let from = input.find(key)? + key.len();
168
169        // Look for the first thing that isn't a base 10 digit or whitespace,
170        // i.e. a comma (denoting another JSON field), curly brace (end of the
171        // object), etc. This'll give us the op number, maybe with a little
172        // whitespace.
173        let to = input.get(from..)?.find(&[',', '}'] as &[_])?;
174        // We might have some whitespace, so let's trim this.
175        let clean = input.get(from..from + to)?.trim();
176
177        T::from_str(clean).ok()
178    }
179}
180
181struct GatewayEventVisitor<'a>(u8, Option<Cow<'a, str>>);
182
183impl GatewayEventVisitor<'_> {
184    fn field<'de, T: Deserialize<'de>, V: MapAccess<'de>>(
185        map: &mut V,
186        field: Field,
187    ) -> Result<T, V::Error> {
188        let mut found = None;
189
190        loop {
191            match map.next_key::<Field>() {
192                Ok(Some(key)) if key == field => found = Some(map.next_value()?),
193                Ok(Some(_)) | Err(_) => {
194                    map.next_value::<IgnoredAny>()?;
195                }
196                Ok(None) => {
197                    break;
198                }
199            }
200        }
201
202        found.ok_or_else(|| {
203            DeError::missing_field(match field {
204                Field::D => "d",
205                Field::Op => "op",
206                Field::S => "s",
207                Field::T => "t",
208            })
209        })
210    }
211
212    fn ignore_all<'de, V: MapAccess<'de>>(map: &mut V) -> Result<(), V::Error> {
213        while let Ok(Some(_)) | Err(_) = map.next_key::<Field>() {
214            map.next_value::<IgnoredAny>()?;
215        }
216
217        Ok(())
218    }
219}
220
221impl<'de> Visitor<'de> for GatewayEventVisitor<'_> {
222    type Value = GatewayEvent;
223
224    fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
225        formatter.write_str("struct GatewayEvent")
226    }
227
228    #[allow(clippy::too_many_lines)]
229    fn visit_map<V>(self, mut map: V) -> Result<GatewayEvent, V::Error>
230    where
231        V: MapAccess<'de>,
232    {
233        static VALID_OPCODES: &[&str] = &[
234            "EVENT",
235            "HEARTBEAT",
236            "HEARTBEAT_ACK",
237            "HELLO",
238            "IDENTIFY",
239            "INVALID_SESSION",
240            "RECONNECT",
241        ];
242
243        let op_deser: U8Deserializer<V::Error> = self.0.into_deserializer();
244
245        let op = OpCode::deserialize(op_deser).ok().ok_or_else(|| {
246            let unexpected = Unexpected::Unsigned(u64::from(self.0));
247
248            DeError::invalid_value(unexpected, &"an opcode")
249        })?;
250
251        Ok(match op {
252            OpCode::Dispatch => {
253                let t = self
254                    .1
255                    .ok_or_else(|| DeError::custom("event type not provided beforehand"))?;
256
257                let mut d = None;
258                let mut s = None;
259
260                loop {
261                    let key = match map.next_key() {
262                        Ok(Some(key)) => key,
263                        Ok(None) => break,
264                        Err(_) => {
265                            map.next_value::<IgnoredAny>()?;
266
267                            continue;
268                        }
269                    };
270
271                    match key {
272                        Field::D => {
273                            if d.is_some() {
274                                return Err(DeError::duplicate_field("d"));
275                            }
276
277                            let deserializer = DispatchEventWithTypeDeserializer::new(&t);
278
279                            d = Some(map.next_value_seed(deserializer)?);
280                        }
281                        Field::S => {
282                            if s.is_some() {
283                                return Err(DeError::duplicate_field("s"));
284                            }
285
286                            s = Some(map.next_value()?);
287                        }
288                        Field::Op | Field::T => {
289                            map.next_value::<IgnoredAny>()?;
290                        }
291                    }
292                }
293
294                let d = d.ok_or_else(|| DeError::missing_field("d"))?;
295                let s = s.ok_or_else(|| DeError::missing_field("s"))?;
296
297                GatewayEvent::Dispatch(s, d)
298            }
299            OpCode::Heartbeat => {
300                Self::ignore_all(&mut map)?;
301
302                GatewayEvent::Heartbeat
303            }
304            OpCode::HeartbeatAck => {
305                Self::ignore_all(&mut map)?;
306
307                GatewayEvent::HeartbeatAck
308            }
309            OpCode::Hello => {
310                let hello = Self::field::<Hello, _>(&mut map, Field::D)?;
311
312                Self::ignore_all(&mut map)?;
313
314                GatewayEvent::Hello(hello)
315            }
316            OpCode::InvalidSession => {
317                let invalidate = Self::field::<bool, _>(&mut map, Field::D)?;
318
319                Self::ignore_all(&mut map)?;
320
321                GatewayEvent::InvalidateSession(invalidate)
322            }
323            OpCode::Identify => return Err(DeError::unknown_variant("Identify", VALID_OPCODES)),
324            OpCode::Reconnect => {
325                Self::ignore_all(&mut map)?;
326
327                GatewayEvent::Reconnect
328            }
329            OpCode::RequestGuildMembers => {
330                return Err(DeError::unknown_variant(
331                    "RequestGuildMembers",
332                    VALID_OPCODES,
333                ))
334            }
335            OpCode::Resume => return Err(DeError::unknown_variant("Resume", VALID_OPCODES)),
336            OpCode::PresenceUpdate => {
337                return Err(DeError::unknown_variant("PresenceUpdate", VALID_OPCODES))
338            }
339            OpCode::VoiceStateUpdate => {
340                return Err(DeError::unknown_variant("VoiceStateUpdate", VALID_OPCODES))
341            }
342        })
343    }
344}
345
346impl<'de> DeserializeSeed<'de> for GatewayEventDeserializer<'_> {
347    type Value = GatewayEvent;
348
349    fn deserialize<D: Deserializer<'de>>(self, deserializer: D) -> Result<Self::Value, D::Error> {
350        const FIELDS: &[&str] = &["op", "d", "s", "t"];
351
352        deserializer.deserialize_struct(
353            "GatewayEvent",
354            FIELDS,
355            GatewayEventVisitor(self.op, self.event_type),
356        )
357    }
358}
359
360impl Serialize for GatewayEvent {
361    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
362        const fn opcode(gateway_event: &GatewayEvent) -> OpCode {
363            match gateway_event {
364                GatewayEvent::Dispatch(_, _) => OpCode::Dispatch,
365                GatewayEvent::Heartbeat => OpCode::Heartbeat,
366                GatewayEvent::HeartbeatAck => OpCode::HeartbeatAck,
367                GatewayEvent::Hello(_) => OpCode::Hello,
368                GatewayEvent::InvalidateSession(_) => OpCode::InvalidSession,
369                GatewayEvent::Reconnect => OpCode::Reconnect,
370            }
371        }
372
373        let mut s = serializer.serialize_struct("GatewayEvent", 4)?;
374
375        if let Self::Dispatch(sequence, event) = self {
376            s.serialize_field("t", &event.kind())?;
377            s.serialize_field("s", &sequence)?;
378            s.serialize_field("op", &opcode(self))?;
379            s.serialize_field("d", &event)?;
380
381            return s.end();
382        }
383
384        // S and T are always null when not a Dispatch event
385        s.serialize_field("t", &None::<&str>)?;
386        s.serialize_field("s", &None::<u64>)?;
387        s.serialize_field("op", &opcode(self))?;
388
389        match self {
390            Self::Dispatch(_, _) => unreachable!("dispatch already handled"),
391            Self::Hello(hello) => {
392                s.serialize_field("d", &hello)?;
393            }
394            Self::InvalidateSession(invalidate) => {
395                s.serialize_field("d", &invalidate)?;
396            }
397            Self::Heartbeat | Self::HeartbeatAck | Self::Reconnect => {
398                s.serialize_field("d", &None::<u64>)?;
399            }
400        }
401
402        s.end()
403    }
404}
405
406#[cfg(test)]
407mod tests {
408    use super::{DispatchEvent, GatewayEvent, GatewayEventDeserializer, OpCode};
409    use crate::{
410        gateway::payload::incoming::{Hello, RoleDelete},
411        id::Id,
412        test::image_hash,
413    };
414    use serde::de::DeserializeSeed;
415    use serde_json::de::Deserializer;
416    use serde_test::Token;
417
418    #[test]
419    fn deserialize_dispatch_role_delete() {
420        let input = r#"{
421            "d": {
422                "guild_id": "1",
423                "role_id": "2"
424            },
425            "op": 0,
426            "s": 7,
427            "t": "GUILD_ROLE_DELETE"
428        }"#;
429
430        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
431        let mut json_deserializer = Deserializer::from_str(input);
432        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
433        assert!(matches!(event, GatewayEvent::Dispatch(7, _)));
434    }
435
436    #[test]
437    fn deserialize_dispatch_guild_update() {
438        let input = format!(
439            r#"{{
440  "d": {{
441    "afk_channel_id": "1337",
442    "afk_timeout": 300,
443    "application_id": null,
444    "banner": null,
445    "default_message_notifications": 0,
446    "description": null,
447    "discovery_splash": null,
448    "emojis": [
449      {{
450        "animated": false,
451        "available": true,
452        "id": "1338",
453        "managed": false,
454        "name": "goodboi",
455        "require_colons": true,
456        "roles": []
457      }}
458    ],
459    "explicit_content_filter": 0,
460    "features": [
461      "INVITE_SPLASH",
462      "ANIMATED_ICON"
463    ],
464    "guild_id": "1339",
465    "icon": "{icon}",
466    "id": "13310",
467    "max_members": 250000,
468    "max_presences": null,
469    "mfa_level": 0,
470    "name": "FooBaz",
471    "nsfw_level": 1,
472    "owner_id": "13311",
473    "preferred_locale": "en-US",
474    "premium_progress_bar_enabled": true,
475    "premium_subscription_count": 4,
476    "premium_tier": 1,
477    "region": "eu-central",
478    "roles": [
479      {{
480        "color": 0,
481        "hoist": false,
482        "id": "13312",
483        "managed": false,
484        "mentionable": false,
485        "name": "@everyone",
486        "permissions": "104193601",
487        "position": 0,
488        "flags": 0
489      }}
490    ],
491    "rules_channel_id": null,
492    "splash": "{splash}",
493    "system_channel_flags": 0,
494    "system_channel_id": "13313",
495    "vanity_url_code": null,
496    "verification_level": 0,
497    "widget_channel_id": null,
498    "widget_enabled": false
499  }},
500  "op": 0,
501  "s": 42,
502  "t": "GUILD_UPDATE"
503}}"#,
504            icon = image_hash::ICON_INPUT,
505            splash = image_hash::SPLASH_INPUT,
506        );
507
508        let deserializer = GatewayEventDeserializer::from_json(&input).unwrap();
509        let mut json_deserializer = Deserializer::from_str(&input);
510        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
511
512        assert!(matches!(event, GatewayEvent::Dispatch(42, _)));
513    }
514
515    #[test]
516    fn deserialize_dispatch_guild_update_2() {
517        let input = format!(
518            r#"{{
519  "d": {{
520    "afk_channel_id": null,
521    "afk_timeout": 300,
522    "application_id": null,
523    "banner": null,
524    "default_message_notifications": 0,
525    "description": null,
526    "discovery_splash": null,
527    "emojis": [
528      {{
529        "animated": false,
530        "available": true,
531        "id": "42",
532        "managed": false,
533        "name": "emmet",
534        "require_colons": true,
535        "roles": []
536      }}
537    ],
538    "explicit_content_filter": 2,
539    "features": [],
540    "guild_id": "43",
541    "icon": "{icon}",
542    "id": "45",
543    "max_members": 250000,
544    "max_presences": null,
545    "mfa_level": 0,
546    "name": "FooBar",
547    "nsfw_level": 0,
548    "owner_id": "46",
549    "preferred_locale": "en-US",
550    "premium_progress_bar_enabled": false,
551    "premium_subscription_count": null,
552    "premium_tier": 0,
553    "region": "us-central",
554    "roles": [
555      {{
556        "color": 0,
557        "hoist": false,
558        "id": "47",
559        "managed": false,
560        "mentionable": false,
561        "name": "@everyone",
562        "permissions": "104324673",
563        "position": 0,
564        "flags": 0
565      }}
566    ],
567    "rules_channel_id": null,
568    "splash": null,
569    "system_channel_flags": 0,
570    "system_channel_id": "48",
571    "vanity_url_code": null,
572    "verification_level": 4,
573    "widget_channel_id": null,
574    "widget_enabled": true
575  }},
576  "op": 0,
577  "s": 1190911,
578  "t": "GUILD_UPDATE"
579}}"#,
580            icon = image_hash::ICON_INPUT
581        );
582
583        let deserializer = GatewayEventDeserializer::from_json(&input).unwrap();
584        let mut json_deserializer = Deserializer::from_str(&input);
585        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
586
587        assert!(matches!(event, GatewayEvent::Dispatch(1_190_911, _)));
588    }
589
590    // Test that events which are not documented to have any data will not fail if
591    // they contain it
592    #[test]
593    fn deserialize_dispatch_resumed() {
594        let input = r#"{
595  "t": "RESUMED",
596  "s": 37448,
597  "op": 0,
598  "d": {
599    "_trace": [
600      "[\"gateway-prd-main-zqnl\",{\"micros\":11488,\"calls\":[\"discord-sessions-prd-1-38\",{\"micros\":1756}]}]"
601    ]
602  }
603}"#;
604
605        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
606        let mut json_deserializer = Deserializer::from_str(input);
607        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
608
609        assert!(matches!(event, GatewayEvent::Dispatch(_, _)));
610    }
611
612    #[test]
613    fn deserialize_heartbeat() {
614        let input = r#"{
615            "t": null,
616            "s": null,
617            "op": 1,
618            "d": null
619        }"#;
620
621        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
622        let mut json_deserializer = Deserializer::from_str(input);
623        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
624
625        assert!(matches!(event, GatewayEvent::Heartbeat));
626    }
627
628    #[test]
629    fn deserialize_heartbeat_ack() {
630        let input = r#"{
631            "t": null,
632            "s": null,
633            "op": 11,
634            "d": null
635        }"#;
636
637        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
638        let mut json_deserializer = Deserializer::from_str(input);
639        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
640
641        assert!(matches!(event, GatewayEvent::HeartbeatAck));
642    }
643
644    #[test]
645    fn deserialize_hello() {
646        let input = r#"{
647            "t": null,
648            "s": null,
649            "op": 10,
650            "d": {
651                "heartbeat_interval": 41250,
652                "_trace": [
653                    "[\"gateway-prd-main-mjmw\",{\"micros\":0.0}]"
654                ]
655            }
656        }"#;
657
658        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
659        let mut json_deserializer = Deserializer::from_str(input);
660        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
661
662        assert!(matches!(
663            event,
664            GatewayEvent::Hello(Hello {
665                heartbeat_interval: 41_250
666            })
667        ));
668    }
669
670    #[test]
671    fn deserialize_invalidate_session() {
672        let input = r#"{
673            "t": null,
674            "s": null,
675            "op": 9,
676            "d": true
677        }"#;
678
679        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
680        let mut json_deserializer = Deserializer::from_str(input);
681        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
682
683        assert!(matches!(event, GatewayEvent::InvalidateSession(true)));
684    }
685
686    #[test]
687    fn deserialize_reconnect() {
688        let input = r#"{
689            "t": null,
690            "s": null,
691            "op": 7,
692            "d": null
693        }"#;
694
695        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
696        let mut json_deserializer = Deserializer::from_str(input);
697        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
698
699        assert!(matches!(event, GatewayEvent::Reconnect));
700    }
701
702    /// Test that the deserializer won't mess up on a nested "t" in user input
703    /// while searching for the event type.
704    #[test]
705    fn deserializer_from_json_nested_quotes() {
706        let input = r#"{
707            "t": "DOESNT_MATTER",
708            "s": 5144,
709            "op": 0,
710            "d": {
711                "name": "a \"t\"role"
712            }
713        }"#;
714
715        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
716        assert_eq!(deserializer.event_type(), Some("DOESNT_MATTER"));
717        assert_eq!(deserializer.op, 0);
718    }
719
720    // Test that the GatewayEventDeserializer handles non-string (read: null)
721    // event types. For example HeartbeatAck
722    #[allow(unused)]
723    #[test]
724    fn deserializer_handles_null_event_types() {
725        let input = r#"{"t":null,"op":11}"#;
726
727        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
728        let mut json_deserializer = Deserializer::from_str(input);
729        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
730
731        assert!(matches!(event, GatewayEvent::HeartbeatAck));
732    }
733
734    #[test]
735    fn serialize_dispatch() {
736        let role_delete = RoleDelete {
737            guild_id: Id::new(1),
738            role_id: Id::new(2),
739        };
740        let value = GatewayEvent::Dispatch(2_048, DispatchEvent::RoleDelete(role_delete));
741
742        serde_test::assert_ser_tokens(
743            &value,
744            &[
745                Token::Struct {
746                    name: "GatewayEvent",
747                    len: 4,
748                },
749                Token::Str("t"),
750                Token::UnitVariant {
751                    name: "EventType",
752                    variant: "GUILD_ROLE_DELETE",
753                },
754                Token::Str("s"),
755                Token::U64(2_048),
756                Token::Str("op"),
757                Token::U8(OpCode::Dispatch as u8),
758                Token::Str("d"),
759                Token::Struct {
760                    name: "RoleDelete",
761                    len: 2,
762                },
763                Token::Str("guild_id"),
764                Token::NewtypeStruct { name: "Id" },
765                Token::Str("1"),
766                Token::Str("role_id"),
767                Token::NewtypeStruct { name: "Id" },
768                Token::Str("2"),
769                Token::StructEnd,
770                Token::StructEnd,
771            ],
772        );
773    }
774
775    #[test]
776    fn serialize_heartbeat() {
777        serde_test::assert_ser_tokens(
778            &GatewayEvent::Heartbeat,
779            &[
780                Token::Struct {
781                    name: "GatewayEvent",
782                    len: 4,
783                },
784                Token::Str("t"),
785                Token::None,
786                Token::Str("s"),
787                Token::None,
788                Token::Str("op"),
789                Token::U8(OpCode::Heartbeat as u8),
790                Token::Str("d"),
791                Token::None,
792                Token::StructEnd,
793            ],
794        );
795    }
796
797    #[test]
798    fn serialize_heartbeat_ack() {
799        serde_test::assert_ser_tokens(
800            &GatewayEvent::HeartbeatAck,
801            &[
802                Token::Struct {
803                    name: "GatewayEvent",
804                    len: 4,
805                },
806                Token::Str("t"),
807                Token::None,
808                Token::Str("s"),
809                Token::None,
810                Token::Str("op"),
811                Token::U8(OpCode::HeartbeatAck as u8),
812                Token::Str("d"),
813                Token::None,
814                Token::StructEnd,
815            ],
816        );
817    }
818
819    #[test]
820    fn serialize_hello() {
821        serde_test::assert_ser_tokens(
822            &GatewayEvent::Hello(Hello {
823                heartbeat_interval: 41250,
824            }),
825            &[
826                Token::Struct {
827                    name: "GatewayEvent",
828                    len: 4,
829                },
830                Token::Str("t"),
831                Token::None,
832                Token::Str("s"),
833                Token::None,
834                Token::Str("op"),
835                Token::U8(OpCode::Hello as u8),
836                Token::Str("d"),
837                Token::Struct {
838                    name: "Hello",
839                    len: 1,
840                },
841                Token::Str("heartbeat_interval"),
842                Token::U64(41250),
843                Token::StructEnd,
844                Token::StructEnd,
845            ],
846        );
847    }
848
849    #[test]
850    fn serialize_invalidate() {
851        let value = GatewayEvent::InvalidateSession(true);
852
853        serde_test::assert_ser_tokens(
854            &value,
855            &[
856                Token::Struct {
857                    name: "GatewayEvent",
858                    len: 4,
859                },
860                Token::Str("t"),
861                Token::None,
862                Token::Str("s"),
863                Token::None,
864                Token::Str("op"),
865                Token::U8(OpCode::InvalidSession as u8),
866                Token::Str("d"),
867                Token::Bool(true),
868                Token::StructEnd,
869            ],
870        );
871    }
872
873    #[test]
874    fn serialize_reconnect() {
875        serde_test::assert_ser_tokens(
876            &GatewayEvent::Reconnect,
877            &[
878                Token::Struct {
879                    name: "GatewayEvent",
880                    len: 4,
881                },
882                Token::Str("t"),
883                Token::None,
884                Token::Str("s"),
885                Token::None,
886                Token::Str("op"),
887                Token::U8(OpCode::Reconnect as u8),
888                Token::Str("d"),
889                Token::None,
890                Token::StructEnd,
891            ],
892        );
893    }
894}