essences/
chats.rs

1use crate::prelude::*;
2
3use serde::{Deserialize, Serialize};
4use strum::Display;
5use strum_macros::EnumString;
6
7#[tsify_next::declare]
8pub type ChatId = uuid::Uuid;
9
10#[tsify_next::declare]
11pub type ChatMessageId = uuid::Uuid;
12
13#[derive(
14    Clone, Debug, Display, Deserialize, Serialize, EnumString, JsonSchema, Tsify, PartialEq, Eq,
15)]
16#[tsify(from_wasm_abi, into_wasm_abi)]
17pub enum ChatType {
18    Global,
19    Private,
20    GlobalSystem,
21    PersonalSystem,
22    // Forward-compat catch-all: any chat type this build does not know
23    // deserializes here instead of failing (and dropping e.g. the whole
24    // chat list on older clients). Deserialize-only — the server must never
25    // construct or send `Unknown`; treat it as unsupported wherever it is
26    // matched. Deliberately a non-doc comment: a doc comment would become a
27    // `description` in the JSON schema and split the enum into a `oneOf`,
28    // changing the shape the client codegen consumes.
29    #[serde(other)]
30    Unknown,
31}
32
33#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema, Tsify)]
34#[tsify(from_wasm_abi, into_wasm_abi)]
35pub struct ChatSettings {
36    #[schemars(title = "Тип чата")]
37    pub chat_type: ChatType,
38
39    #[schemars(title = "Может ли пользователь писать в чат")]
40    pub user_allowed_to_write: bool,
41
42    #[schemars(title = "Получает ли пользователь уведомление при любом сообщении")]
43    pub get_notification_on_messages: bool,
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, JsonSchema, Tsify)]
47#[tsify(from_wasm_abi, into_wasm_abi)]
48pub struct ChatMessage {
49    pub id: Option<ChatMessageId>,
50    pub sender_character_id: uuid::Uuid,
51    pub recipient_character_id: Option<uuid::Uuid>,
52    pub chat_id: Option<ChatId>,
53    pub chat_type: ChatType,
54    pub text: String,
55    pub created_at: chrono::DateTime<chrono::Utc>,
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Tsify)]
59#[tsify(from_wasm_abi, into_wasm_abi)]
60pub struct Chat {
61    pub id: ChatId,
62    pub chat_type: ChatType,
63    pub messages: Vec<ChatMessage>,
64    pub last_read_message_id: Option<ChatMessageId>,
65}
66
67#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Tsify)]
68#[tsify(from_wasm_abi, into_wasm_abi)]
69pub struct ReadMessage {
70    pub chat_id: ChatId,
71    pub message_id: ChatMessageId,
72}
73
74#[derive(Debug, Clone, Serialize, Deserialize, JsonSchema, Tsify)]
75#[tsify(from_wasm_abi, into_wasm_abi)]
76pub struct ReportChatMessage {
77    pub message_id: ChatMessageId,
78}
79
80#[cfg(test)]
81mod tests {
82    use super::*;
83
84    #[test]
85    fn known_chat_types_round_trip_unchanged() {
86        for (chat_type, json) in [
87            (ChatType::Global, r#""Global""#),
88            (ChatType::Private, r#""Private""#),
89            (ChatType::GlobalSystem, r#""GlobalSystem""#),
90            (ChatType::PersonalSystem, r#""PersonalSystem""#),
91        ] {
92            assert_eq!(serde_json::to_string(&chat_type).unwrap(), json);
93            assert_eq!(serde_json::from_str::<ChatType>(json).unwrap(), chat_type);
94        }
95    }
96
97    /// A chat type introduced by a newer server must not fail
98    /// deserialization on an older client — it maps to `Unknown`.
99    #[test]
100    fn unknown_chat_type_string_deserializes_to_unknown() {
101        let chat_type: ChatType = serde_json::from_str(r#""GuildChat""#).unwrap();
102        assert_eq!(chat_type, ChatType::Unknown);
103    }
104
105    /// Client converters are generated from the JSON schema, so the schema
106    /// must list `Unknown` for clients to be able to map new types onto it.
107    #[test]
108    fn schema_includes_unknown_variant() {
109        let schema = serde_json::to_value(schemars::schema_for!(ChatType)).unwrap();
110        let values: Vec<&str> = schema["enum"]
111            .as_array()
112            .expect("ChatType schema must be a plain enum")
113            .iter()
114            .map(|v| v.as_str().unwrap())
115            .collect();
116        assert_eq!(
117            values,
118            [
119                "Global",
120                "Private",
121                "GlobalSystem",
122                "PersonalSystem",
123                "Unknown"
124            ]
125        );
126    }
127}