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#[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#[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 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 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 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 #[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 pub fn event_type(&self) -> Option<&str> {
113 self.event_type.as_deref()
114 }
115
116 pub const fn op(&self) -> u8 {
118 self.op
119 }
120
121 pub const fn sequence(&self) -> Option<u64> {
126 self.sequence
127 }
128
129 fn find_event_type(input: &'a str) -> Option<&'a str> {
130 let from = input.find(r#""t":"#)? + 4;
137
138 let start = input.get(from..)?.find(|c: char| !c.is_whitespace())? + from + 1;
142
143 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 let from = input.find(key)? + key.len();
168
169 let to = input.get(from..)?.find(&[',', '}'] as &[_])?;
174 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.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]
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]
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 #[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}