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