Skip to main content

ruma_client_api/typing/
create_typing_event.rs

1//! `PUT /_matrix/client/*/rooms/{roomId}/typing/{userId}`
2//!
3//! Send a typing event to a room.
4
5pub mod v3 {
6    //! `/v3/` ([spec])
7    //!
8    //! [spec]: https://spec.matrix.org/latest/client-server-api/#put_matrixclientv3roomsroomidtypinguserid
9
10    use std::time::Duration;
11
12    use ruma_common::{
13        OwnedRoomId, OwnedUserId,
14        api::{auth_scheme::AccessToken, request, response},
15        metadata,
16    };
17    use serde::{Deserialize, Deserializer, Serialize, de::Error};
18
19    metadata! {
20        method: PUT,
21        authentication: AccessToken,
22        rate_limited: true,
23        history: {
24            1.0 => "/_matrix/client/r0/rooms/{room_id}/typing/{user_id}",
25            1.1 => "/_matrix/client/v3/rooms/{room_id}/typing/{user_id}",
26        }
27    }
28
29    /// Request type for the `create_typing_event` endpoint.
30    #[request(error = crate::Error)]
31    pub struct Request {
32        /// The room in which the user is typing.
33        #[ruma_api(path)]
34        pub room_id: OwnedRoomId,
35
36        /// The user who has started to type.
37        #[ruma_api(path)]
38        pub user_id: OwnedUserId,
39
40        /// Whether the user is typing within a length of time or not.
41        #[ruma_api(body)]
42        pub state: Typing,
43    }
44
45    /// Response type for the `create_typing_event` endpoint.
46    #[response(error = crate::Error)]
47    #[derive(Default)]
48    pub struct Response {}
49
50    impl Request {
51        /// Creates a new `Request` with the given user ID, room ID and typing state.
52        pub fn new(user_id: OwnedUserId, room_id: OwnedRoomId, state: Typing) -> Self {
53            Self { user_id, room_id, state }
54        }
55    }
56
57    impl Response {
58        /// Creates an empty `Response`.
59        pub fn new() -> Self {
60            Self {}
61        }
62    }
63
64    /// A mark for whether the user is typing or not.
65    #[derive(Clone, Copy, Debug)]
66    #[allow(clippy::exhaustive_enums)]
67    pub enum Typing {
68        /// The user is currently not typing.
69        No,
70
71        /// The user is currently typing.
72        Yes(TypingInfo),
73    }
74
75    impl From<TypingInfo> for Typing {
76        fn from(value: TypingInfo) -> Self {
77            Self::Yes(value)
78        }
79    }
80
81    /// Details about the user currently typing.
82    #[derive(Clone, Copy, Debug)]
83    #[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
84    pub struct TypingInfo {
85        /// The length of time to mark this user as typing.
86        pub timeout: Duration,
87    }
88
89    impl TypingInfo {
90        /// Create a new `TypingInfo` with the given timeout.
91        pub fn new(timeout: Duration) -> Self {
92            Self { timeout }
93        }
94    }
95
96    #[derive(Deserialize, Serialize)]
97    struct TypingSerdeRepr {
98        typing: bool,
99
100        #[serde(
101            with = "ruma_common::serde::duration::opt_ms",
102            default,
103            skip_serializing_if = "Option::is_none"
104        )]
105        timeout: Option<Duration>,
106    }
107
108    impl Serialize for Typing {
109        fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
110        where
111            S: serde::Serializer,
112        {
113            let repr = match self {
114                Self::No => TypingSerdeRepr { typing: false, timeout: None },
115                Self::Yes(TypingInfo { timeout }) => {
116                    TypingSerdeRepr { typing: true, timeout: Some(*timeout) }
117                }
118            };
119
120            repr.serialize(serializer)
121        }
122    }
123
124    impl<'de> Deserialize<'de> for Typing {
125        fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
126        where
127            D: Deserializer<'de>,
128        {
129            let repr = TypingSerdeRepr::deserialize(deserializer)?;
130
131            Ok(if repr.typing {
132                Typing::Yes(TypingInfo {
133                    timeout: repr.timeout.ok_or_else(|| D::Error::missing_field("timeout"))?,
134                })
135            } else {
136                Typing::No
137            })
138        }
139    }
140}