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