1use super::{
2 super::OpCode, DispatchEvent, DispatchEventWithTypeDeserializer, Event, EventConversionError,
3};
4use crate::gateway::payload::incoming::Hello;
5use serde::{
6 Deserialize, Serialize,
7 de::{
8 DeserializeSeed, Deserializer, Error as DeError, IgnoredAny, IntoDeserializer, MapAccess,
9 Unexpected, Visitor, value::U8Deserializer,
10 },
11 ser::{SerializeStruct, Serializer},
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 "colors": {{
482 "primary_color": 0
483 }},
484 "hoist": false,
485 "id": "13312",
486 "managed": false,
487 "mentionable": false,
488 "name": "@everyone",
489 "permissions": "104193601",
490 "position": 0,
491 "flags": 0
492 }}
493 ],
494 "rules_channel_id": null,
495 "splash": "{splash}",
496 "system_channel_flags": 0,
497 "system_channel_id": "13313",
498 "vanity_url_code": null,
499 "verification_level": 0,
500 "widget_channel_id": null,
501 "widget_enabled": false
502 }},
503 "op": 0,
504 "s": 42,
505 "t": "GUILD_UPDATE"
506}}"#,
507 icon = image_hash::ICON_INPUT,
508 splash = image_hash::SPLASH_INPUT,
509 );
510
511 let deserializer = GatewayEventDeserializer::from_json(&input).unwrap();
512 let mut json_deserializer = Deserializer::from_str(&input);
513 let event = deserializer.deserialize(&mut json_deserializer).unwrap();
514
515 assert!(matches!(event, GatewayEvent::Dispatch(42, _)));
516 }
517
518 #[test]
519 fn deserialize_dispatch_guild_update_2() {
520 let input = format!(
521 r#"{{
522 "d": {{
523 "afk_channel_id": null,
524 "afk_timeout": 300,
525 "application_id": null,
526 "banner": null,
527 "default_message_notifications": 0,
528 "description": null,
529 "discovery_splash": null,
530 "emojis": [
531 {{
532 "animated": false,
533 "available": true,
534 "id": "42",
535 "managed": false,
536 "name": "emmet",
537 "require_colons": true,
538 "roles": []
539 }}
540 ],
541 "explicit_content_filter": 2,
542 "features": [],
543 "guild_id": "43",
544 "icon": "{icon}",
545 "id": "45",
546 "max_members": 250000,
547 "max_presences": null,
548 "mfa_level": 0,
549 "name": "FooBar",
550 "nsfw_level": 0,
551 "owner_id": "46",
552 "preferred_locale": "en-US",
553 "premium_progress_bar_enabled": false,
554 "premium_subscription_count": null,
555 "premium_tier": 0,
556 "region": "us-central",
557 "roles": [
558 {{
559 "color": 0,
560 "colors": {{
561 "primary_color": 0
562 }},
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]
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": null
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));
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]
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 #[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,
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::None,
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}