twilight_model/gateway/presence/
activity_button.rs1use serde::{
4 de::{Deserializer, Error as DeError, IgnoredAny, MapAccess, Visitor},
5 ser::{SerializeStruct, Serializer},
6 Deserialize, Serialize,
7};
8use std::fmt::{Formatter, Result as FmtResult};
9
10#[derive(Clone, Debug, Eq, Hash, PartialEq)]
53pub enum ActivityButton {
54 Link(ActivityButtonLink),
56 Text(ActivityButtonText),
58 Unknown,
60}
61
62impl ActivityButton {
63 pub const fn is_link(&self) -> bool {
65 matches!(self, Self::Link(_))
66 }
67
68 pub const fn is_text(&self) -> bool {
70 matches!(self, Self::Text(_))
71 }
72
73 #[allow(clippy::missing_const_for_fn)]
75 pub fn label(&self) -> Option<&str> {
76 match self {
77 Self::Link(link) => Some(&link.label),
78 Self::Text(text) => Some(&text.label),
79 Self::Unknown => None,
80 }
81 }
82
83 #[allow(clippy::missing_const_for_fn)]
86 pub fn url(&self) -> Option<&str> {
87 if let Self::Link(link) = self {
88 Some(&link.url)
89 } else {
90 None
91 }
92 }
93}
94
95#[derive(Debug, Deserialize)]
96#[serde(field_identifier, rename_all = "snake_case")]
97enum ActivityButtonField {
98 Label,
99 Url,
100}
101
102struct ActivityButtonVisitor;
103
104impl<'de> Visitor<'de> for ActivityButtonVisitor {
105 type Value = ActivityButton;
106
107 fn expecting(&self, f: &mut Formatter<'_>) -> FmtResult {
108 f.write_str("activity button struct or string")
109 }
110
111 fn visit_string<E: DeError>(self, v: String) -> Result<Self::Value, E> {
112 Ok(ActivityButton::Text(ActivityButtonText { label: v }))
113 }
114
115 fn visit_str<E: DeError>(self, v: &str) -> Result<Self::Value, E> {
116 Ok(ActivityButton::Text(ActivityButtonText {
117 label: v.to_owned(),
118 }))
119 }
120
121 fn visit_map<A: MapAccess<'de>>(self, mut map: A) -> Result<Self::Value, A::Error> {
122 let mut label = None;
123 let mut url = None;
124
125 loop {
126 let key = match map.next_key() {
127 Ok(Some(key)) => key,
128 Ok(None) => break,
129 Err(_) => {
130 map.next_value::<IgnoredAny>()?;
131
132 continue;
133 }
134 };
135
136 match key {
137 ActivityButtonField::Label => {
138 if label.is_some() {
139 return Err(DeError::duplicate_field("label"));
140 }
141
142 label = Some(map.next_value()?);
143 }
144 ActivityButtonField::Url => {
145 if url.is_some() {
146 return Err(DeError::duplicate_field("url"));
147 }
148
149 url = Some(map.next_value()?);
150 }
151 }
152 }
153
154 let label = label.ok_or_else(|| DeError::missing_field("label"))?;
155 let url = url.ok_or_else(|| DeError::missing_field("url"))?;
156
157 Ok(ActivityButton::Link(ActivityButtonLink { label, url }))
158 }
159}
160
161impl<'de> Deserialize<'de> for ActivityButton {
162 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
163 deserializer.deserialize_any(ActivityButtonVisitor)
164 }
165}
166
167impl Serialize for ActivityButton {
168 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
169 match self {
170 Self::Link(link) => {
171 let mut state = serializer.serialize_struct("ActivityButton", 2)?;
172
173 state.serialize_field("label", &link.label)?;
174 state.serialize_field("url", &link.url)?;
175
176 state.end()
177 }
178 Self::Text(text) => serializer.serialize_str(&text.label),
179 Self::Unknown => Err(serde::ser::Error::custom(
180 "Can't serialize an unknown activity button type",
181 )),
182 }
183 }
184}
185
186#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
188pub struct ActivityButtonLink {
189 pub label: String,
191 pub url: String,
193}
194
195#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
214#[serde(transparent)]
215pub struct ActivityButtonText {
216 pub label: String,
218}
219
220#[cfg(test)]
221mod tests {
222 use super::{ActivityButton, ActivityButtonLink, ActivityButtonText};
223 use serde::{Deserialize, Serialize};
224 use serde_test::Token;
225 use static_assertions::{assert_fields, assert_impl_all};
226 use std::fmt::Debug;
227
228 assert_fields!(ActivityButtonLink: label, url);
229 assert_impl_all!(
230 ActivityButtonLink: Clone,
231 Debug,
232 Deserialize<'static>,
233 Eq,
234 PartialEq,
235 Serialize
236 );
237 assert_fields!(ActivityButtonText: label);
238 assert_impl_all!(
239 ActivityButtonText: Clone,
240 Debug,
241 Deserialize<'static>,
242 Eq,
243 PartialEq,
244 Serialize
245 );
246 assert_impl_all!(
247 ActivityButton: Clone,
248 Debug,
249 Deserialize<'static>,
250 Eq,
251 PartialEq,
252 Serialize
253 );
254
255 fn link() -> ActivityButtonLink {
256 ActivityButtonLink {
257 label: "a".to_owned(),
258 url: "b".to_owned(),
259 }
260 }
261
262 fn text() -> ActivityButtonText {
263 ActivityButtonText {
264 label: "a".to_owned(),
265 }
266 }
267
268 #[test]
269 fn activity_button_link() {
270 serde_test::assert_de_tokens(
271 &link(),
272 &[
273 Token::Struct {
274 name: "ActivityButtonLink",
275 len: 2,
276 },
277 Token::Str("label"),
278 Token::Str("a"),
279 Token::Str("url"),
280 Token::Str("b"),
281 Token::StructEnd,
282 ],
283 );
284 }
285
286 #[test]
287 fn activity_button_text() {
288 serde_test::assert_de_tokens(&text(), &[Token::Str("a")]);
289 }
290
291 #[test]
292 fn activity_button_with_url() {
293 serde_test::assert_tokens(
294 &ActivityButton::Link(link()),
295 &[
296 Token::Struct {
297 name: "ActivityButton",
298 len: 2,
299 },
300 Token::Str("label"),
301 Token::Str("a"),
302 Token::Str("url"),
303 Token::Str("b"),
304 Token::StructEnd,
305 ],
306 );
307 }
308
309 #[test]
310 fn activity_button_without_url() {
311 serde_test::assert_tokens(&ActivityButton::Text(text()), &[Token::Str("a")]);
312 }
313}