twilight-rs / twilight

Powerful, flexible, and scalable ecosystem of Rust libraries for the Discord API.
https://discord.gg/twilight-rs
ISC License
652 stars 130 forks source link

twilight-http: `Client::update_role_positions` uses wrong structure format #2338

Closed Aeledfyr closed 1 month ago

Aeledfyr commented 3 months ago

The Client::update_role_positions function always errors, as discords API expects a different format from what it provides. The error:

Response error: status code 400, error: {"message": "Invalid Form Body", "code": 50035, "errors": {"0": {"_errors": [{"code": "DICT_TYPE_CONVERT", "message": "Only dictionaries may be used in a DictType"}]}, "1": {"_errors": [{"code": "DICT_TYPE_CONVERT", "message": "Only dictionaries may be used in a DictType"}]}}}

The issue is in twilight-http/src/request/guild/role/update_role_positions.rs;

impl TryIntoRequest for UpdateRolePositions<'_> {
    fn try_into_request(self) -> Result<Request, Error> {
        Request::builder(&Route::UpdateRolePositions {
            guild_id: self.guild_id.get(),
        })
        .json(&self.roles)
        .build()
    }
}

Where self.roles is &[(Id<RoleMarker>, u64)], which will be serialized by serde_json as a 2d array, like [[id1, pos1], [id2, pos2]].

By Discord API docs for Modify Guild Role Positions, the API expects an array of dictionaries, each with an id and position field; such as [{"id":id1,"position":pos1},{"id":id2,"position":pos2}].

My quick but inefficient patch was to remap the format and allocate a Vec:

#[derive(serde::Serialize)]
struct RolePosition {
    id: Id<RoleMarker>,
    position: Option<u64>,
}
let roles = self.roles.iter()
    .map(|(i, p)| RolePosition { id: *i, position: Some(*p) })
    .collect::<Vec<_>>();
request = request.json(&roles)?;

But it would probably be better to change the API to take in a list of RolePositions directly to avoid the internal reallocation. (Similar to the API used for update_guild_channel_positions, see #2327.)