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                    continue;
196                }
197                Ok(None) => {
198                    break;
199                }
200            }
201        }
202
203        found.ok_or_else(|| {
204            DeError::missing_field(match field {
205                Field::D => "d",
206                Field::Op => "op",
207                Field::S => "s",
208                Field::T => "t",
209            })
210        })
211    }
212
213    fn ignore_all<'de, V: MapAccess<'de>>(map: &mut V) -> Result<(), V::Error> {
214        while let Ok(Some(_)) | Err(_) = map.next_key::<Field>() {
215            map.next_value::<IgnoredAny>()?;
216        }
217
218        Ok(())
219    }
220}
221
222impl<'de> Visitor<'de> for GatewayEventVisitor<'_> {
223    type Value = GatewayEvent;
224
225    fn expecting(&self, formatter: &mut Formatter<'_>) -> FmtResult {
226        formatter.write_str("struct GatewayEvent")
227    }
228
229    #[allow(clippy::too_many_lines)]
230    fn visit_map<V>(self, mut map: V) -> Result<GatewayEvent, V::Error>
231    where
232        V: MapAccess<'de>,
233    {
234        static VALID_OPCODES: &[&str] = &[
235            "EVENT",
236            "HEARTBEAT",
237            "HEARTBEAT_ACK",
238            "HELLO",
239            "IDENTIFY",
240            "INVALID_SESSION",
241            "RECONNECT",
242        ];
243
244        let op_deser: U8Deserializer<V::Error> = self.0.into_deserializer();
245
246        let op = OpCode::deserialize(op_deser).ok().ok_or_else(|| {
247            let unexpected = Unexpected::Unsigned(u64::from(self.0));
248
249            DeError::invalid_value(unexpected, &"an opcode")
250        })?;
251
252        Ok(match op {
253            OpCode::Dispatch => {
254                let t = self
255                    .1
256                    .ok_or_else(|| DeError::custom("event type not provided beforehand"))?;
257
258                let mut d = None;
259                let mut s = None;
260
261                loop {
262                    let key = match map.next_key() {
263                        Ok(Some(key)) => key,
264                        Ok(None) => break,
265                        Err(_) => {
266                            map.next_value::<IgnoredAny>()?;
267
268                            continue;
269                        }
270                    };
271
272                    match key {
273                        Field::D => {
274                            if d.is_some() {
275                                return Err(DeError::duplicate_field("d"));
276                            }
277
278                            let deserializer = DispatchEventWithTypeDeserializer::new(&t);
279
280                            d = Some(map.next_value_seed(deserializer)?);
281                        }
282                        Field::S => {
283                            if s.is_some() {
284                                return Err(DeError::duplicate_field("s"));
285                            }
286
287                            s = Some(map.next_value()?);
288                        }
289                        Field::Op | Field::T => {
290                            map.next_value::<IgnoredAny>()?;
291                        }
292                    }
293                }
294
295                let d = d.ok_or_else(|| DeError::missing_field("d"))?;
296                let s = s.ok_or_else(|| DeError::missing_field("s"))?;
297
298                GatewayEvent::Dispatch(s, d)
299            }
300            OpCode::Heartbeat => {
301                Self::ignore_all(&mut map)?;
302
303                GatewayEvent::Heartbeat
304            }
305            OpCode::HeartbeatAck => {
306                Self::ignore_all(&mut map)?;
307
308                GatewayEvent::HeartbeatAck
309            }
310            OpCode::Hello => {
311                let hello = Self::field::<Hello, _>(&mut map, Field::D)?;
312
313                Self::ignore_all(&mut map)?;
314
315                GatewayEvent::Hello(hello)
316            }
317            OpCode::InvalidSession => {
318                let invalidate = Self::field::<bool, _>(&mut map, Field::D)?;
319
320                Self::ignore_all(&mut map)?;
321
322                GatewayEvent::InvalidateSession(invalidate)
323            }
324            OpCode::Identify => return Err(DeError::unknown_variant("Identify", VALID_OPCODES)),
325            OpCode::Reconnect => {
326                Self::ignore_all(&mut map)?;
327
328                GatewayEvent::Reconnect
329            }
330            OpCode::RequestGuildMembers => {
331                return Err(DeError::unknown_variant(
332                    "RequestGuildMembers",
333                    VALID_OPCODES,
334                ))
335            }
336            OpCode::Resume => return Err(DeError::unknown_variant("Resume", VALID_OPCODES)),
337            OpCode::PresenceUpdate => {
338                return Err(DeError::unknown_variant("PresenceUpdate", VALID_OPCODES))
339            }
340            OpCode::VoiceStateUpdate => {
341                return Err(DeError::unknown_variant("VoiceStateUpdate", VALID_OPCODES))
342            }
343        })
344    }
345}
346
347impl<'de> DeserializeSeed<'de> for GatewayEventDeserializer<'_> {
348    type Value = GatewayEvent;
349
350    fn deserialize<D: Deserializer<'de>>(self, deserializer: D) -> Result<Self::Value, D::Error> {
351        const FIELDS: &[&str] = &["op", "d", "s", "t"];
352
353        deserializer.deserialize_struct(
354            "GatewayEvent",
355            FIELDS,
356            GatewayEventVisitor(self.op, self.event_type),
357        )
358    }
359}
360
361impl Serialize for GatewayEvent {
362    fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
363        const fn opcode(gateway_event: &GatewayEvent) -> OpCode {
364            match gateway_event {
365                GatewayEvent::Dispatch(_, _) => OpCode::Dispatch,
366                GatewayEvent::Heartbeat => OpCode::Heartbeat,
367                GatewayEvent::HeartbeatAck => OpCode::HeartbeatAck,
368                GatewayEvent::Hello(_) => OpCode::Hello,
369                GatewayEvent::InvalidateSession(_) => OpCode::InvalidSession,
370                GatewayEvent::Reconnect => OpCode::Reconnect,
371            }
372        }
373
374        let mut s = serializer.serialize_struct("GatewayEvent", 4)?;
375
376        if let Self::Dispatch(sequence, event) = self {
377            s.serialize_field("t", &event.kind())?;
378            s.serialize_field("s", &sequence)?;
379            s.serialize_field("op", &opcode(self))?;
380            s.serialize_field("d", &event)?;
381
382            return s.end();
383        }
384
385        // S and T are always null when not a Dispatch event
386        s.serialize_field("t", &None::<&str>)?;
387        s.serialize_field("s", &None::<u64>)?;
388        s.serialize_field("op", &opcode(self))?;
389
390        match self {
391            Self::Dispatch(_, _) => unreachable!("dispatch already handled"),
392            Self::Hello(hello) => {
393                s.serialize_field("d", &hello)?;
394            }
395            Self::InvalidateSession(invalidate) => {
396                s.serialize_field("d", &invalidate)?;
397            }
398            Self::Heartbeat | Self::HeartbeatAck | Self::Reconnect => {
399                s.serialize_field("d", &None::<u64>)?;
400            }
401        }
402
403        s.end()
404    }
405}
406
407#[cfg(test)]
408mod tests {
409    use super::{DispatchEvent, GatewayEvent, GatewayEventDeserializer, OpCode};
410    use crate::{
411        gateway::payload::incoming::{Hello, RoleDelete},
412        id::Id,
413        test::image_hash,
414    };
415    use serde::de::DeserializeSeed;
416    use serde_json::de::Deserializer;
417    use serde_test::Token;
418
419    #[test]
420    fn deserialize_dispatch_role_delete() {
421        let input = r#"{
422            "d": {
423                "guild_id": "1",
424                "role_id": "2"
425            },
426            "op": 0,
427            "s": 7,
428            "t": "GUILD_ROLE_DELETE"
429        }"#;
430
431        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
432        let mut json_deserializer = Deserializer::from_str(input);
433        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
434        assert!(matches!(event, GatewayEvent::Dispatch(7, _)));
435    }
436
437    #[test]
438    fn deserialize_dispatch_guild_update() {
439        let input = format!(
440            r#"{{
441  "d": {{
442    "afk_channel_id": "1337",
443    "afk_timeout": 300,
444    "application_id": null,
445    "banner": null,
446    "default_message_notifications": 0,
447    "description": null,
448    "discovery_splash": null,
449    "emojis": [
450      {{
451        "animated": false,
452        "available": true,
453        "id": "1338",
454        "managed": false,
455        "name": "goodboi",
456        "require_colons": true,
457        "roles": []
458      }}
459    ],
460    "explicit_content_filter": 0,
461    "features": [
462      "INVITE_SPLASH",
463      "ANIMATED_ICON"
464    ],
465    "guild_id": "1339",
466    "icon": "{icon}",
467    "id": "13310",
468    "max_members": 250000,
469    "max_presences": null,
470    "mfa_level": 0,
471    "name": "FooBaz",
472    "nsfw_level": 1,
473    "owner_id": "13311",
474    "preferred_locale": "en-US",
475    "premium_progress_bar_enabled": true,
476    "premium_subscription_count": 4,
477    "premium_tier": 1,
478    "region": "eu-central",
479    "roles": [
480      {{
481        "color": 0,
482        "hoist": false,
483        "id": "13312",
484        "managed": false,
485        "mentionable": false,
486        "name": "@everyone",
487        "permissions": "104193601",
488        "position": 0,
489        "flags": 0
490      }}
491    ],
492    "rules_channel_id": null,
493    "splash": "{splash}",
494    "system_channel_flags": 0,
495    "system_channel_id": "13313",
496    "vanity_url_code": null,
497    "verification_level": 0,
498    "widget_channel_id": null,
499    "widget_enabled": false
500  }},
501  "op": 0,
502  "s": 42,
503  "t": "GUILD_UPDATE"
504}}"#,
505            icon = image_hash::ICON_INPUT,
506            splash = image_hash::SPLASH_INPUT,
507        );
508
509        let deserializer = GatewayEventDeserializer::from_json(&input).unwrap();
510        let mut json_deserializer = Deserializer::from_str(&input);
511        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
512
513        assert!(matches!(event, GatewayEvent::Dispatch(42, _)));
514    }
515
516    #[test]
517    fn deserialize_dispatch_guild_update_2() {
518        let input = format!(
519            r#"{{
520  "d": {{
521    "afk_channel_id": null,
522    "afk_timeout": 300,
523    "application_id": null,
524    "banner": null,
525    "default_message_notifications": 0,
526    "description": null,
527    "discovery_splash": null,
528    "emojis": [
529      {{
530        "animated": false,
531        "available": true,
532        "id": "42",
533        "managed": false,
534        "name": "emmet",
535        "require_colons": true,
536        "roles": []
537      }}
538    ],
539    "explicit_content_filter": 2,
540    "features": [],
541    "guild_id": "43",
542    "icon": "{icon}",
543    "id": "45",
544    "max_members": 250000,
545    "max_presences": null,
546    "mfa_level": 0,
547    "name": "FooBar",
548    "nsfw_level": 0,
549    "owner_id": "46",
550    "preferred_locale": "en-US",
551    "premium_progress_bar_enabled": false,
552    "premium_subscription_count": null,
553    "premium_tier": 0,
554    "region": "us-central",
555    "roles": [
556      {{
557        "color": 0,
558        "hoist": false,
559        "id": "47",
560        "managed": false,
561        "mentionable": false,
562        "name": "@everyone",
563        "permissions": "104324673",
564        "position": 0,
565        "flags": 0
566      }}
567    ],
568    "rules_channel_id": null,
569    "splash": null,
570    "system_channel_flags": 0,
571    "system_channel_id": "48",
572    "vanity_url_code": null,
573    "verification_level": 4,
574    "widget_channel_id": null,
575    "widget_enabled": true
576  }},
577  "op": 0,
578  "s": 1190911,
579  "t": "GUILD_UPDATE"
580}}"#,
581            icon = image_hash::ICON_INPUT
582        );
583
584        let deserializer = GatewayEventDeserializer::from_json(&input).unwrap();
585        let mut json_deserializer = Deserializer::from_str(&input);
586        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
587
588        assert!(matches!(event, GatewayEvent::Dispatch(1_190_911, _)));
589    }
590
591    // Test that events which are not documented to have any data will not fail if
592    // they contain it
593    #[test]
594    fn deserialize_dispatch_resumed() {
595        let input = r#"{
596  "t": "RESUMED",
597  "s": 37448,
598  "op": 0,
599  "d": {
600    "_trace": [
601      "[\"gateway-prd-main-zqnl\",{\"micros\":11488,\"calls\":[\"discord-sessions-prd-1-38\",{\"micros\":1756}]}]"
602    ]
603  }
604}"#;
605
606        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
607        let mut json_deserializer = Deserializer::from_str(input);
608        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
609
610        assert!(matches!(event, GatewayEvent::Dispatch(_, _)));
611    }
612
613    #[test]
614    fn deserialize_heartbeat() {
615        let input = r#"{
616            "t": null,
617            "s": null,
618            "op": 1,
619            "d": null
620        }"#;
621
622        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
623        let mut json_deserializer = Deserializer::from_str(input);
624        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
625
626        assert!(matches!(event, GatewayEvent::Heartbeat));
627    }
628
629    #[test]
630    fn deserialize_heartbeat_ack() {
631        let input = r#"{
632            "t": null,
633            "s": null,
634            "op": 11,
635            "d": null
636        }"#;
637
638        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
639        let mut json_deserializer = Deserializer::from_str(input);
640        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
641
642        assert!(matches!(event, GatewayEvent::HeartbeatAck));
643    }
644
645    #[test]
646    fn deserialize_hello() {
647        let input = r#"{
648            "t": null,
649            "s": null,
650            "op": 10,
651            "d": {
652                "heartbeat_interval": 41250,
653                "_trace": [
654                    "[\"gateway-prd-main-mjmw\",{\"micros\":0.0}]"
655                ]
656            }
657        }"#;
658
659        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
660        let mut json_deserializer = Deserializer::from_str(input);
661        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
662
663        assert!(matches!(
664            event,
665            GatewayEvent::Hello(Hello {
666                heartbeat_interval: 41_250
667            })
668        ));
669    }
670
671    #[test]
672    fn deserialize_invalidate_session() {
673        let input = r#"{
674            "t": null,
675            "s": null,
676            "op": 9,
677            "d": true
678        }"#;
679
680        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
681        let mut json_deserializer = Deserializer::from_str(input);
682        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
683
684        assert!(matches!(event, GatewayEvent::InvalidateSession(true)));
685    }
686
687    #[test]
688    fn deserialize_reconnect() {
689        let input = r#"{
690            "t": null,
691            "s": null,
692            "op": 7,
693            "d": null
694        }"#;
695
696        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
697        let mut json_deserializer = Deserializer::from_str(input);
698        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
699
700        assert!(matches!(event, GatewayEvent::Reconnect));
701    }
702
703    /// Test that the deserializer won't mess up on a nested "t" in user input
704    /// while searching for the event type.
705    #[test]
706    fn deserializer_from_json_nested_quotes() {
707        let input = r#"{
708            "t": "DOESNT_MATTER",
709            "s": 5144,
710            "op": 0,
711            "d": {
712                "name": "a \"t\"role"
713            }
714        }"#;
715
716        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
717        assert_eq!(deserializer.event_type(), Some("DOESNT_MATTER"));
718        assert_eq!(deserializer.op, 0);
719    }
720
721    // Test that the GatewayEventDeserializer handles non-string (read: null)
722    // event types. For example HeartbeatAck
723    #[allow(unused)]
724    #[test]
725    fn deserializer_handles_null_event_types() {
726        let input = r#"{"t":null,"op":11}"#;
727
728        let deserializer = GatewayEventDeserializer::from_json(input).unwrap();
729        let mut json_deserializer = Deserializer::from_str(input);
730        let event = deserializer.deserialize(&mut json_deserializer).unwrap();
731
732        assert!(matches!(event, GatewayEvent::HeartbeatAck));
733    }
734
735    #[test]
736    fn serialize_dispatch() {
737        let role_delete = RoleDelete {
738            guild_id: Id::new(1),
739            role_id: Id::new(2),
740        };
741        let value = GatewayEvent::Dispatch(2_048, DispatchEvent::RoleDelete(role_delete));
742
743        serde_test::assert_ser_tokens(
744            &value,
745            &[
746                Token::Struct {
747                    name: "GatewayEvent",
748                    len: 4,
749                },
750                Token::Str("t"),
751                Token::UnitVariant {
752                    name: "EventType",
753                    variant: "GUILD_ROLE_DELETE",
754                },
755                Token::Str("s"),
756                Token::U64(2_048),
757                Token::Str("op"),
758                Token::U8(OpCode::Dispatch as u8),
759                Token::Str("d"),
760                Token::Struct {
761                    name: "RoleDelete",
762                    len: 2,
763                },
764                Token::Str("guild_id"),
765                Token::NewtypeStruct { name: "Id" },
766                Token::Str("1"),
767                Token::Str("role_id"),
768                Token::NewtypeStruct { name: "Id" },
769                Token::Str("2"),
770                Token::StructEnd,
771                Token::StructEnd,
772            ],
773        );
774    }
775
776    #[test]
777    fn serialize_heartbeat() {
778        serde_test::assert_ser_tokens(
779            &GatewayEvent::Heartbeat,
780            &[
781                Token::Struct {
782                    name: "GatewayEvent",
783                    len: 4,
784                },
785                Token::Str("t"),
786                Token::None,
787                Token::Str("s"),
788                Token::None,
789                Token::Str("op"),
790                Token::U8(OpCode::Heartbeat as u8),
791                Token::Str("d"),
792                Token::None,
793                Token::StructEnd,
794            ],
795        );
796    }
797
798    #[test]
799    fn serialize_heartbeat_ack() {
800        serde_test::assert_ser_tokens(
801            &GatewayEvent::HeartbeatAck,
802            &[
803                Token::Struct {
804                    name: "GatewayEvent",
805                    len: 4,
806                },
807                Token::Str("t"),
808                Token::None,
809                Token::Str("s"),
810                Token::None,
811                Token::Str("op"),
812                Token::U8(OpCode::HeartbeatAck as u8),
813                Token::Str("d"),
814                Token::None,
815                Token::StructEnd,
816            ],
817        );
818    }
819
820    #[test]
821    fn serialize_hello() {
822        serde_test::assert_ser_tokens(
823            &GatewayEvent::Hello(Hello {
824                heartbeat_interval: 41250,
825            }),
826            &[
827                Token::Struct {
828                    name: "GatewayEvent",
829                    len: 4,
830                },
831                Token::Str("t"),
832                Token::None,
833                Token::Str("s"),
834                Token::None,
835                Token::Str("op"),
836                Token::U8(OpCode::Hello as u8),
837                Token::Str("d"),
838                Token::Struct {
839                    name: "Hello",
840                    len: 1,
841                },
842                Token::Str("heartbeat_interval"),
843                Token::U64(41250),
844                Token::StructEnd,
845                Token::StructEnd,
846            ],
847        );
848    }
849
850    #[test]
851    fn serialize_invalidate() {
852        let value = GatewayEvent::InvalidateSession(true);
853
854        serde_test::assert_ser_tokens(
855            &value,
856            &[
857                Token::Struct {
858                    name: "GatewayEvent",
859                    len: 4,
860                },
861                Token::Str("t"),
862                Token::None,
863                Token::Str("s"),
864                Token::None,
865                Token::Str("op"),
866                Token::U8(OpCode::InvalidSession as u8),
867                Token::Str("d"),
868                Token::Bool(true),
869                Token::StructEnd,
870            ],
871        );
872    }
873
874    #[test]
875    fn serialize_reconnect() {
876        serde_test::assert_ser_tokens(
877            &GatewayEvent::Reconnect,
878            &[
879                Token::Struct {
880                    name: "GatewayEvent",
881                    len: 4,
882                },
883                Token::Str("t"),
884                Token::None,
885                Token::Str("s"),
886                Token::None,
887                Token::Str("op"),
888                Token::U8(OpCode::Reconnect as u8),
889                Token::Str("d"),
890                Token::None,
891                Token::StructEnd,
892            ],
893        );
894    }
895}