From 0ca035c5478e51029405dce4bdb3cc2eccebda20 Mon Sep 17 00:00:00 2001 From: Hampus Date: Mon, 28 Sep 2026 01:10:52 +0200 Subject: [PATCH] fix(messages): accept null version on legacy message rows (#3004) --- fluxer_messages/src/shard_impl.rs | 42 ++++++++++++++++++++++--------- fluxer_messages/src/types.rs | 14 ++++++++++- 2 files changed, 43 insertions(+), 13 deletions(-) diff --git a/fluxer_messages/src/shard_impl.rs b/fluxer_messages/src/shard_impl.rs index b03ad1b1a..e079c9d4f 100644 --- a/fluxer_messages/src/shard_impl.rs +++ b/fluxer_messages/src/shard_impl.rs @@ -2382,8 +2382,6 @@ fn decode_postgres_message(row: serde_json::Value) -> anyhow::Result { .get("pinned_timestamp") .is_some_and(|value| !value.is_null()); row.insert("pinned".to_owned(), serde_json::Value::Bool(pinned)); - default_i32_field(&mut row, "type", 0); - default_i32_field(&mut row, "version", 0); Ok(serde_json::from_value(serde_json::Value::Object(row))?) } @@ -2415,16 +2413,6 @@ fn decode_postgres_attachment_decay( Ok((row.attachment_id, expires_at)) } -fn default_i32_field( - row: &mut serde_json::Map, - field: &str, - value: i32, -) { - if row.get(field).is_none_or(serde_json::Value::is_null) { - row.insert(field.to_owned(), serde_json::Value::Number(value.into())); - } -} - #[cfg(feature = "scylla")] fn rows_to_messages(rows: QueryRowsResult) -> anyhow::Result> { let rows: Vec = rows.rows::()?.collect::>()?; @@ -3404,6 +3392,36 @@ mod tests { ); } + #[test] + fn build_responses_request_accepts_legacy_null_version_rows() { + let request: MessageRequest = serde_json::from_value(json!({ + "op": "BuildResponses", + "messages": [{ + "message_id": "1449544529132171273", + "channel_id": "1431572375251247158", + "bucket": 399, + "author_id": "1130650140672000000", + "type": null, + "version": null, + "content": "" + }], + "viewer_user_id": "1130650140672000000", + "source_guild_id": null, + "message_history_cutoff_ms": null, + "can_read_message_history": true, + "media_endpoint": "https://media.example", + "media_proxy_secret_key": "secret", + "include_reactions": true + })) + .unwrap(); + + let MessageRequest::BuildResponses { messages, .. } = request else { + panic!("expected BuildResponses"); + }; + assert_eq!(messages[0].message_type, 0); + assert_eq!(messages[0].version, 0); + } + #[test] fn mention_context_carries_embed_user_ids_for_message_and_snapshots() { let message: Message = serde_json::from_value(json!({ diff --git a/fluxer_messages/src/types.rs b/fluxer_messages/src/types.rs index 3360e445c..09a46853d 100644 --- a/fluxer_messages/src/types.rs +++ b/fluxer_messages/src/types.rs @@ -58,6 +58,13 @@ mod serde_id { .map(StringOrI64::into_i64) .collect() } + + pub fn i32_or_null_as_default<'de, D>(deserializer: D) -> Result + where + D: Deserializer<'de>, + { + Ok(Option::::deserialize(deserializer)?.unwrap_or_default()) + } } #[derive(Debug, Clone, Serialize, Deserialize)] @@ -164,7 +171,11 @@ pub struct Message { pub bucket: i32, #[serde(default, deserialize_with = "serde_id::opt_i64_from_string_or_number")] pub author_id: Option, - #[serde(rename = "type")] + #[serde( + rename = "type", + default, + deserialize_with = "serde_id::i32_or_null_as_default" + )] pub message_type: i32, #[serde(default, deserialize_with = "serde_id::opt_i64_from_string_or_number")] pub webhook_id: Option, @@ -191,6 +202,7 @@ pub struct Message { )] pub mention_channels: Vec, pub has_reaction: Option, + #[serde(default, deserialize_with = "serde_id::i32_or_null_as_default")] pub version: i32, #[serde(skip_serializing_if = "Option::is_none")] pub attachments: Option>,