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