1use js_int::UInt;
6use ruma_common::{OwnedMxcUri, OwnedUserId, presence::PresenceState};
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::{
76 canonical_json::assert_to_canonical_json_eq, mxc_uri, presence::PresenceState,
77 };
78 use serde_json::{from_value as from_json_value, json};
79
80 use super::{PresenceEvent, PresenceEventContent};
81
82 #[test]
83 fn serialization() {
84 let content = PresenceEventContent {
85 avatar_url: Some(mxc_uri!("mxc://localhost/wefuiwegh8742w").to_owned()),
86 currently_active: Some(false),
87 displayname: None,
88 last_active_ago: Some(uint!(2_478_593)),
89 presence: PresenceState::Online,
90 status_msg: Some("Making cupcakes".into()),
91 };
92
93 assert_to_canonical_json_eq!(
94 content,
95 json!({
96 "avatar_url": "mxc://localhost/wefuiwegh8742w",
97 "currently_active": false,
98 "last_active_ago": 2_478_593,
99 "presence": "online",
100 "status_msg": "Making cupcakes",
101 }),
102 );
103 }
104
105 #[test]
106 fn deserialization() {
107 let json = json!({
108 "content": {
109 "avatar_url": "mxc://localhost/wefuiwegh8742w",
110 "currently_active": false,
111 "last_active_ago": 2_478_593,
112 "presence": "online",
113 "status_msg": "Making cupcakes"
114 },
115 "sender": "@example:localhost",
116 "type": "m.presence"
117 });
118
119 let ev = from_json_value::<PresenceEvent>(json).unwrap();
120 assert_eq!(
121 ev.content.avatar_url.as_deref(),
122 Some(mxc_uri!("mxc://localhost/wefuiwegh8742w"))
123 );
124 assert_eq!(ev.content.currently_active, Some(false));
125 assert_eq!(ev.content.displayname, None);
126 assert_eq!(ev.content.last_active_ago, Some(uint!(2_478_593)));
127 assert_eq!(ev.content.presence, PresenceState::Online);
128 assert_eq!(ev.content.status_msg.as_deref(), Some("Making cupcakes"));
129 assert_eq!(ev.sender, "@example:localhost");
130
131 #[cfg(feature = "compat-empty-string-null")]
132 {
133 let json = json!({
134 "content": {
135 "avatar_url": "",
136 "currently_active": false,
137 "last_active_ago": 2_478_593,
138 "presence": "online",
139 "status_msg": "Making cupcakes"
140 },
141 "sender": "@example:localhost",
142 "type": "m.presence"
143 });
144
145 let ev = from_json_value::<PresenceEvent>(json).unwrap();
146 assert_eq!(ev.content.avatar_url, None);
147 assert_eq!(ev.content.currently_active, Some(false));
148 assert_eq!(ev.content.displayname, None);
149 assert_eq!(ev.content.last_active_ago, Some(uint!(2_478_593)));
150 assert_eq!(ev.content.presence, PresenceState::Online);
151 assert_eq!(ev.content.status_msg.as_deref(), Some("Making cupcakes"));
152 assert_eq!(ev.sender, "@example:localhost");
153 }
154 }
155}