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