1use js_int::UInt;
6use ruma_common::{presence::PresenceState, OwnedMxcUri, OwnedUserId};
7use serde::{Deserialize, Serialize};
8
9#[derive(Clone, Debug, Serialize, Deserialize)]
11#[allow(clippy::exhaustive_structs)]
12#[serde(tag = "type", rename = "m.presence")]
13pub struct PresenceEvent {
14 pub content: PresenceEventContent,
16
17 pub sender: OwnedUserId,
19}
20
21#[derive(Clone, Debug, Deserialize, Serialize)]
25#[cfg_attr(not(ruma_unstable_exhaustive_types), non_exhaustive)]
26pub struct PresenceEventContent {
27 #[serde(skip_serializing_if = "Option::is_none")]
32 #[cfg_attr(
33 feature = "compat-empty-string-null",
34 serde(default, deserialize_with = "ruma_common::serde::empty_string_as_none")
35 )]
36 pub avatar_url: Option<OwnedMxcUri>,
37
38 #[serde(skip_serializing_if = "Option::is_none")]
40 pub currently_active: Option<bool>,
41
42 #[serde(skip_serializing_if = "Option::is_none")]
44 pub displayname: Option<String>,
45
46 #[serde(skip_serializing_if = "Option::is_none")]
48 pub last_active_ago: Option<UInt>,
49
50 pub presence: PresenceState,
52
53 #[serde(skip_serializing_if = "Option::is_none")]
55 pub status_msg: Option<String>,
56}
57
58impl PresenceEventContent {
59 pub fn new(presence: PresenceState) -> Self {
61 Self {
62 avatar_url: None,
63 currently_active: None,
64 displayname: None,
65 last_active_ago: None,
66 presence,
67 status_msg: None,
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use js_int::uint;
75 use ruma_common::{mxc_uri, presence::PresenceState};
76 use serde_json::{from_value as from_json_value, json, to_value as to_json_value};
77
78 use super::{PresenceEvent, PresenceEventContent};
79
80 #[test]
81 fn serialization() {
82 let content = PresenceEventContent {
83 avatar_url: Some(mxc_uri!("mxc://localhost/wefuiwegh8742w").to_owned()),
84 currently_active: Some(false),
85 displayname: None,
86 last_active_ago: Some(uint!(2_478_593)),
87 presence: PresenceState::Online,
88 status_msg: Some("Making cupcakes".into()),
89 };
90
91 let json = json!({
92 "avatar_url": "mxc://localhost/wefuiwegh8742w",
93 "currently_active": false,
94 "last_active_ago": 2_478_593,
95 "presence": "online",
96 "status_msg": "Making cupcakes"
97 });
98
99 assert_eq!(to_json_value(&content).unwrap(), json);
100 }
101
102 #[test]
103 fn deserialization() {
104 let json = json!({
105 "content": {
106 "avatar_url": "mxc://localhost/wefuiwegh8742w",
107 "currently_active": false,
108 "last_active_ago": 2_478_593,
109 "presence": "online",
110 "status_msg": "Making cupcakes"
111 },
112 "sender": "@example:localhost",
113 "type": "m.presence"
114 });
115
116 let ev = from_json_value::<PresenceEvent>(json).unwrap();
117 assert_eq!(
118 ev.content.avatar_url.as_deref(),
119 Some(mxc_uri!("mxc://localhost/wefuiwegh8742w"))
120 );
121 assert_eq!(ev.content.currently_active, Some(false));
122 assert_eq!(ev.content.displayname, None);
123 assert_eq!(ev.content.last_active_ago, Some(uint!(2_478_593)));
124 assert_eq!(ev.content.presence, PresenceState::Online);
125 assert_eq!(ev.content.status_msg.as_deref(), Some("Making cupcakes"));
126 assert_eq!(ev.sender, "@example:localhost");
127
128 #[cfg(feature = "compat-empty-string-null")]
129 {
130 let json = json!({
131 "content": {
132 "avatar_url": "",
133 "currently_active": false,
134 "last_active_ago": 2_478_593,
135 "presence": "online",
136 "status_msg": "Making cupcakes"
137 },
138 "sender": "@example:localhost",
139 "type": "m.presence"
140 });
141
142 let ev = from_json_value::<PresenceEvent>(json).unwrap();
143 assert_eq!(ev.content.avatar_url, None);
144 assert_eq!(ev.content.currently_active, Some(false));
145 assert_eq!(ev.content.displayname, None);
146 assert_eq!(ev.content.last_active_ago, Some(uint!(2_478_593)));
147 assert_eq!(ev.content.presence, PresenceState::Online);
148 assert_eq!(ev.content.status_msg.as_deref(), Some("Making cupcakes"));
149 assert_eq!(ev.sender, "@example:localhost");
150 }
151 }
152}