Repository: serenity-rs/serenity (language: rust)

Issue:
Discord gateway event `GUILD_MEMBERS_CHUNK` is treated as an `UnknownEvent`
I have recently upgraded my code to use the latest and shiniest version to date, 0.12. While updating my code base, I noticed that the event handler for guild members chunks wasn't triggering anymore.

When enabling the debug output of Serenity, the events *do* in fact come through, but are not recognized as what they're supposed to be.

The guilds were requested as such, in the cache ready handler:
```rust
// `ctx` is a plain ol' `&Context`
// `guild_id` is a `&GuildId` in a for loop
ctx.shard.chunk_guild(*guild_id, None, false, gateway::ChunkGuildFilter::None, None);
```

The event handler for guild members chunks is simply doing some logging and calling another function that is responsible for one part of the handling:
```rust
#[async_trait]
impl EventHandler for Handler {
    // ...

    #[tracing::instrument(skip_all, fields(guild_id = ?chunk.guild_id, page = chunk.chunk_index + 1, total = chunk.chunk_count))]
    async fn guild_members_chunk(&self, ctx: Context, chunk: GuildMembersChunkEvent) {
        info!("Received a chunk of {} members from the guild", chunk.members.len());
        process_handler_result(crate::modules::datasync::guild_chunk(&ctx, &chunk).await);
    }
}
```

## Current behavior
The logging outputs similar to what follows:
```
2023-12-17T20:50:32.183720Z DEBUG run: serenity::client::dispatch: An unknown event was received: UnknownEvent { kind: "GUILD_MEMBERS_CHUNK", value: /* the whole payload */ }
```

## Expected behavior
The event is properly recognized as a guild members chunk and the appropriate event handler is called

## Environment information
- Rust version: `1.74.1`
- OS: Fedora Linux 39
- Serenity: as previously mentioned, `0.12.0`


Discussion / hints:
I think it's time to share a few findings, in case I'm not the only one trying to catch the fish or somebody in the future finds a similar issue.

I have started writing a workaround (at least, what I thought was one) to deserialize the event manually and somehow call the proper event handler, using the raw event handler as such:

```rust
pub struct RawHandler;

#[async_trait]
impl RawEventHandler for RawHandler {
    async fn raw_event(&self, ctx: Context, ev: Event) {
        match ev {
            Event::Unknown(unknown_event) if unknown_event.kind == "GUILD_MEMBERS_CHUNK" => {
                warn!("Using workaround for GUILD_MEMBERS_CHUNK event");
                handle_raw_guild_members_chunk(&ctx, unknown_event).await;
            },

            _ => { /* No-op */ }
        };
    }
}

async fn handle_raw_guild_members_chunk(ctx: &Context, event: UnknownEvent) {
    let event_str = serenity::json::to_string_pretty(&event).unwrap();
    println!("{}", event_str);
    println!("{:?}", event);

    let chunk_event = match serenity::json::from_value::<GuildMembersChunkEvent>(event.value) {
        Ok(chunk_event) => chunk_event,

        Err(why) => {
            error!("Deserialization error\n{:?}", why);
            return;
        }
    };
}
```

I have printed the stringified event and its deserialized representation to later check for discrepancies between Discord's documentation and the implementation of the data structures.

Then, I took this bit of code for a spin by compiling and running the program before continuing writing the code. The deserialization operation returns an error: `Json(Error("invalid type: sequence, expected a map", line: 0, column: 0))`.

When taking a closer look at [Discord's documentation about the `GUILD_MEMBERS_CHUNK` event](https://discord.com/developers/docs/topics/gateway-events#guild-members-chunk), the `members` property is an array. In Rust, that property is [typed as a `HashMap<UserId, Member>`](https://github.com/serenity-rs/serenity/blob/d57a8cf369206c783ae5d9007839dcf0acd9677c/src/model/event.rs#L275).
Thank you for diagnosing this. The culprit is #2287, which simplified the Deserialize impl for `GuildMembersChunkEvent` but forgot to make sure the hashmap was being constructed out of the array rather than being directly deserialized.

Fix patch:
diff --git a/src/model/event.rs b/src/model/event.rs
index 9dbd49ea56d..2aab4c86720 100644
--- a/src/model/event.rs
+++ b/src/model/event.rs
@@ -13,7 +13,14 @@ use serde::Serialize;
 
 use super::application::ActionRow;
 use super::prelude::*;
-use super::utils::{deserialize_val, emojis, remove_from_map, remove_from_map_opt, stickers};
+use super::utils::{
+    deserialize_val,
+    emojis,
+    members,
+    remove_from_map,
+    remove_from_map_opt,
+    stickers,
+};
 use crate::constants::Opcode;
 use crate::internal::prelude::*;
 use crate::model::application::{CommandPermissions, Interaction};
@@ -273,6 +280,7 @@ pub struct GuildMembersChunkEvent {
     /// ID of the guild.
     pub guild_id: GuildId,
     /// Set of guild members.
+    #[serde(with = "members")]
     pub members: HashMap<UserId, Member>,
     /// Chunk index in the expected chunks for this response (0 <= chunk_index < chunk_count).
     pub chunk_index: u32,
diff --git a/src/model/guild/mod.rs b/src/model/guild/mod.rs
index a4ced80ac79..87ae822fab0 100644
--- a/src/model/guild/mod.rs
+++ b/src/model/guild/mod.rs
@@ -256,8 +256,7 @@ pub struct Guild {
     ///
     /// Members might not all be available when the [`ReadyEvent`] is received if the
     /// [`Self::member_count`] is greater than the [`LARGE_THRESHOLD`] set by the library.
-    #[serde(serialize_with = "serialize_map_values")]
-    #[serde(deserialize_with = "deserialize_members")]
+    #[serde(with = "members")]
     pub members: HashMap<UserId, Member>,
     /// All voice and text channels contained within a guild.
     ///
diff --git a/src/model/utils.rs b/src/model/utils.rs
index c8cbf7f61b8..1db4f201b9a 100644
--- a/src/model/utils.rs
+++ b/src/model/utils.rs
@@ -113,10 +113,23 @@ pub fn deserialize_guild_channels<'de, D: Deserializer<'de>>(
     Ok(map)
 }
 
-pub fn deserialize_members<'de, D: Deserializer<'de>>(
-    deserializer: D,
-) -> StdResult<HashMap<UserId, Member>, D::Error> {
-    deserializer.deserialize_seq(SequenceToMapVisitor::new(|member: &Member| member.user.id))
+/// Used with `#[serde(with = "members")]
+pub mod members {
+    use std::collections::HashMap;
+
+    use serde::Deserializer;
+
+    use super::SequenceToMapVisitor;
+    use crate::model::guild::Member;
+    use crate::model::id::UserId;
+
+    pub fn deserialize<'de, D: Deserializer<'de>>(
+        deserializer: D,
+    ) -> Result<HashMap<UserId, Member>, D::Error> {
+        deserializer.deserialize_seq(SequenceToMapVisitor::new(|member: &Member| member.user.id))
+    }
+
+    pub use super::serialize_map_values as serialize;
 }
 
 /// Used with `#[serde(with = "presences")]`


Test patch: