Skip to content

Stronger TypeScript types of number (C-like) enums (e.g. chat_type) #7029

@WofWca

Description

@WofWca

Consider how FullChat.chat_type is defined:

This generates the following TypeScript definition:

"chatType": U32;

which is IMO too weakly typed. This has been an issue in the v2 migration in Delta Chat Desktop, where a new chat_type has been added, TypeScript did not assist us here.

I have tried to solve this with the following patch, but for some reason it turns chatType into a string union (type ChatChattype = ("Single" | "Group" | "Mailinglist" | "OutBroadcast" | "InBroadcast");)

patch
diff --git a/Cargo.lock b/Cargo.lock
index d967b3aae..bff535463 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1408,6 +1408,7 @@ dependencies = [
  "schemars",
  "serde",
  "serde_json",
+ "serde_repr",
  "tempfile",
  "tokio",
  "typescript-type-def",
@@ -5403,6 +5404,17 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "serde_repr"
+version = "0.1.20"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "175ee3e80ae9982737ca543e96133087cbd9a485eecc3bc4de9c1a37b47ea59c"
+dependencies = [
+ "proc-macro2",
+ "quote",
+ "syn 2.0.104",
+]
+
 [[package]]
 name = "serde_spanned"
 version = "0.6.9"
diff --git a/deltachat-jsonrpc/Cargo.toml b/deltachat-jsonrpc/Cargo.toml
index 1b64fda1e..1357a0b1c 100644
--- a/deltachat-jsonrpc/Cargo.toml
+++ b/deltachat-jsonrpc/Cargo.toml
@@ -13,6 +13,7 @@ deltachat-contact-tools = { workspace = true }
 num-traits = { workspace = true }
 schemars = "0.8.22"
 serde = { workspace = true, features = ["derive"] }
+serde_repr = "0.1"
 async-channel = { workspace = true }
 serde_json = { workspace = true }
 yerpc = { workspace = true, features = ["anyhow_expose", "openrpc"] }
diff --git a/deltachat-jsonrpc/src/api/types/chat.rs b/deltachat-jsonrpc/src/api/types/chat.rs
index e75892bb7..9f2a70448 100644
--- a/deltachat-jsonrpc/src/api/types/chat.rs
+++ b/deltachat-jsonrpc/src/api/types/chat.rs
@@ -13,6 +13,49 @@
 use super::color_int_to_hex_string;
 use super::contact::ContactObject;
 
+// https://serde.rs/enum-number.html
+// https://graham.cool/schemars/examples/8-enum_repr/
+#[derive(
+    serde_repr::Serialize_repr,
+    serde_repr::Deserialize_repr,
+    schemars::JsonSchema_repr,
+    PartialEq,
+    TypeDef,
+)]
+#[repr(u32)]
+enum ChatChattype {
+    Single = 100,
+    Group = 120,
+    Mailinglist = 140,
+    OutBroadcast = 160,
+    InBroadcast = 165,
+}
+
+// TODO use newtype pattern, untagged stuff IDK.
+impl From<Chattype> for ChatChattype {
+    fn from(chattype: Chattype) -> Self {
+        match chattype {
+            Chattype::Single => ChatChattype::Single,
+            Chattype::Group => ChatChattype::Group,
+            Chattype::Mailinglist => ChatChattype::Mailinglist,
+            Chattype::OutBroadcast => ChatChattype::OutBroadcast,
+            Chattype::InBroadcast => ChatChattype::InBroadcast,
+        }
+    }
+}
+
+impl From<ChatChattype> for Chattype {
+    fn from(chattype: ChatChattype) -> Self {
+        match chattype {
+            ChatChattype::Single => Chattype::Single,
+            ChatChattype::Group => Chattype::Group,
+            ChatChattype::Mailinglist => Chattype::Mailinglist,
+            ChatChattype::OutBroadcast => Chattype::OutBroadcast,
+            ChatChattype::InBroadcast => Chattype::InBroadcast,
+        }
+    }
+}
+
 #[derive(Serialize, TypeDef, schemars::JsonSchema)]
 #[serde(rename_all = "camelCase")]
 pub struct FullChat {
@@ -58,7 +101,7 @@ pub struct FullChat {
     archived: bool,
     pinned: bool,
     // subtitle  - will be moved to frontend because it uses translation functions
-    chat_type: u32,
+    chat_type: ChatChattype,
     is_unpromoted: bool,
     is_self_talk: bool,
     contacts: Vec<ContactObject>,
@@ -136,7 +179,7 @@ pub async fn try_from_dc_chat_id(context: &Context, chat_id: u32) -> Result<Self
             profile_image, //BLOBS ?
             archived: chat.get_visibility() == chat::ChatVisibility::Archived,
             pinned: chat.get_visibility() == chat::ChatVisibility::Pinned,
-            chat_type: chat.get_type().to_u32().context("unknown chat type id")?,
+            chat_type: chat.get_type().into(),
             is_unpromoted: chat.is_unpromoted(),
             is_self_talk: chat.is_self_talk(),
             contacts,

We have a few other such places that use numbers:

pub status: u32, // in reality this is an enum, but for simplicity and because it gets converted into a number anyway, we use an u32 here.

If nobody knows how to fix the repr serialization, I suggest to just add another field chatTypeV2 (for backwards compatibility) that would be a string union.

Note that we're already utilizing such string unions, e.g. here

view_type: MessageViewtype,

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions