nextcloud_talk/
room_type.rs1use std::fmt;
4
5use serde::{Deserialize, Deserializer, Serialize, Serializer};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
14#[repr(u8)]
15pub enum RoomType {
16 OneToOne = 1,
18 Group = 2,
20 Public = 3,
22}
23
24impl RoomType {
25 pub const fn as_u8(self) -> u8 {
27 self as u8
28 }
29
30 pub const fn from_u8(code: u8) -> Option<Self> {
32 match code {
33 1 => Some(Self::OneToOne),
34 2 => Some(Self::Group),
35 3 => Some(Self::Public),
36 _ => None,
37 }
38 }
39
40 pub const fn as_str(self) -> &'static str {
42 match self {
43 Self::OneToOne => "one-to-one",
44 Self::Group => "group",
45 Self::Public => "public",
46 }
47 }
48}
49
50impl Serialize for RoomType {
51 fn serialize<S: Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
52 serializer.serialize_u8(self.as_u8())
53 }
54}
55
56impl<'de> Deserialize<'de> for RoomType {
57 fn deserialize<D: Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
58 struct RoomTypeVisitor;
59
60 impl serde::de::Visitor<'_> for RoomTypeVisitor {
61 type Value = RoomType;
62
63 fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
64 f.write_str("a valid Nextcloud Talk room type code (1, 2, or 3)")
65 }
66
67 fn visit_u64<E: serde::de::Error>(self, v: u64) -> Result<RoomType, E> {
68 let code = u8::try_from(v)
69 .map_err(|_| E::custom(format!("room type code {v} out of range")))?;
70 RoomType::from_u8(code)
71 .ok_or_else(|| E::custom(format!("unknown room type code {code}")))
72 }
73
74 fn visit_i64<E: serde::de::Error>(self, v: i64) -> Result<RoomType, E> {
75 let code = u8::try_from(v)
76 .map_err(|_| E::custom(format!("room type code {v} out of range")))?;
77 RoomType::from_u8(code)
78 .ok_or_else(|| E::custom(format!("unknown room type code {code}")))
79 }
80
81 fn visit_str<E: serde::de::Error>(self, v: &str) -> Result<RoomType, E> {
82 let code: u8 = v
83 .trim()
84 .parse()
85 .map_err(|_| E::custom(format!("invalid room type string '{v}'")))?;
86 RoomType::from_u8(code)
87 .ok_or_else(|| E::custom(format!("unknown room type code {code}")))
88 }
89 }
90
91 deserializer.deserialize_any(RoomTypeVisitor)
92 }
93}
94
95impl fmt::Display for RoomType {
96 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
97 f.write_str(self.as_str())
98 }
99}
100
101impl TryFrom<u8> for RoomType {
102 type Error = UnknownRoomTypeError;
103
104 fn try_from(value: u8) -> Result<Self, Self::Error> {
105 Self::from_u8(value).ok_or(UnknownRoomTypeError(value))
106 }
107}
108
109impl From<RoomType> for u8 {
110 fn from(room_type: RoomType) -> Self {
111 room_type.as_u8()
112 }
113}
114
115#[derive(Clone, Copy, Debug, PartialEq, Eq)]
117pub struct UnknownRoomTypeError(u8);
118
119impl UnknownRoomTypeError {
120 pub const fn code(self) -> u8 {
122 self.0
123 }
124}
125
126impl fmt::Display for UnknownRoomTypeError {
127 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
128 write!(f, "unknown room type code: {}", self.0)
129 }
130}
131
132impl std::error::Error for UnknownRoomTypeError {}
133
134#[cfg(test)]
135mod tests {
136 use super::*;
137
138 #[test]
139 fn room_type_display_and_as_str() {
140 assert_eq!(RoomType::OneToOne.as_str(), "one-to-one");
141 assert_eq!(RoomType::Group.as_str(), "group");
142 assert_eq!(RoomType::Public.as_str(), "public");
143 assert_eq!(RoomType::OneToOne.to_string(), "one-to-one");
144 }
145
146 #[test]
147 fn room_type_conversion_u8() {
148 assert_eq!(RoomType::from_u8(1), Some(RoomType::OneToOne));
149 assert_eq!(RoomType::from_u8(2), Some(RoomType::Group));
150 assert_eq!(RoomType::from_u8(3), Some(RoomType::Public));
151 assert_eq!(RoomType::from_u8(99), None);
152
153 assert_eq!(RoomType::try_from(1), Ok(RoomType::OneToOne));
154 assert_eq!(RoomType::try_from(2), Ok(RoomType::Group));
155 assert_eq!(RoomType::try_from(3), Ok(RoomType::Public));
156 let err = RoomType::try_from(99).unwrap_err();
157 assert_eq!(err.code(), 99);
158 assert_eq!(err.to_string(), "unknown room type code: 99");
159
160 assert_eq!(u8::from(RoomType::OneToOne), 1);
161 assert_eq!(u8::from(RoomType::Group), 2);
162 assert_eq!(u8::from(RoomType::Public), 3);
163 }
164
165 #[test]
166 fn room_type_serde_round_trip() {
167 let serialized = serde_json::to_string(&RoomType::Group).expect("serialize");
168 assert_eq!(serialized, "2");
169 let deserialized: RoomType = serde_json::from_str(&serialized).expect("deserialize");
170 assert_eq!(deserialized, RoomType::Group);
171
172 let from_str: RoomType = serde_json::from_str("\"3\"").expect("deserialize from string");
173 assert_eq!(from_str, RoomType::Public);
174 }
175}