1use std::{concat, convert::TryFrom, fmt::Debug, stringify};
13
14use paste::paste;
15use serde::{Deserialize, Serialize};
16
17pub trait ResourceType:
21 Debug + Clone + PartialEq + Serialize + for<'de> Deserialize<'de> + private::Sealed
22{
23 type Id: ResourceId;
24}
25
26pub trait ResourceId: EntityId {}
30
31pub trait EntityType:
35 Debug + Clone + PartialEq + Serialize + for<'de> Deserialize<'de> + private::Sealed
36{
37 type Id: EntityId;
38}
39
40pub trait EntityId:
44 Debug + Clone + PartialEq + Serialize + for<'de> Deserialize<'de> + private::Sealed
45{
46}
47
48macro_rules! if_unknown {
49 (
50 if Unknown {
51 $($i:item)*
52 } else {
53 $($_:item)*
54 }
55 ) => {
56 $($i)*
57 };
58 (
59 if $not_unknown:ident {
60 $($_:item)*
61 } else {
62 $($i:item)*
63 }
64 ) => {
65 $($i)*
66 };
67}
68
69macro_rules! if_not_unknown {
70 (
71 if Unknown {
72 $($_:item)*
73 }
74 ) => { };
75 (
76 if $not_unknown:ident {
77 $($i:item)*
78 }
79 ) => {
80 $($i)*
81 };
82}
83
84macro_rules! entities {
85 (
86 $(
87 $r:ident = $n:literal,
88 )+
89 ) => {
90 $(
91 paste! {
92 if_unknown! {
94 if $r {
95 #[doc = "The unknown entity type."]
96 #[derive(Debug, Default, Clone, Copy, PartialEq)]
97 #[derive(Serialize, Deserialize)]
98 #[serde(try_from = "u8", into = "u8")]
99 pub struct $r;
100 } else {
101 #[doc = "The " $r:lower " resource type."]
102 #[derive(Debug, Default, Clone, Copy, PartialEq)]
103 #[derive(Serialize, Deserialize)]
104 #[serde(try_from = "u8", into = "u8")]
105 pub struct $r;
106 }
107 }
108
109 impl From<$r> for u8 {
110 fn from(_: $r) -> Self {
111 $n
112 }
113 }
114
115 impl TryFrom<u8> for $r {
116 type Error = &'static str;
117 fn try_from(n: u8) -> Result<Self, Self::Error> {
118 if n == $n {
119 Ok($r)
120 } else {
121 Err(concat!("expected ", $n, " (", stringify!($r), ")"))
122 }
123 }
124 }
125
126 impl private::Sealed for $r {}
127 impl EntityType for $r {
128 type Id = [<$r Id>];
129 }
130 if_not_unknown! {
131 if $r {
132 impl ResourceType for $r {
133 type Id = [<$r Id>];
134 }
135 }
136 }
137
138 if_unknown! {
139 if $r {
140 #[derive(Debug, Default, Clone, PartialEq)]
141 #[derive(Serialize, Deserialize)]
142 #[serde(from = "()", into = "()")]
143 pub struct [<$r Id>];
144 impl From<[<$r Id>]> for () {
145 fn from(_: [<$r Id>]) -> Self {}
146 }
147 impl From<()> for [<$r Id>] {
148 fn from(_: ()) -> Self {
149 Self
150 }
151 }
152 } else {
153 #[derive(Debug, Default, Clone, PartialEq)]
154 #[derive(Serialize, Deserialize)]
155 #[repr(transparent)]
156 pub struct [<$r Id>](pub String);
157 }
158 }
159
160 impl private::Sealed for [<$r Id>] {}
161 impl EntityId for [<$r Id>] {}
162 if_not_unknown! {
163 if $r {
164 impl ResourceId for [<$r Id>] {}
165 }
166 }
167 }
168 )+
169 };
170}
171
172entities! {
173 Unknown = 0,
174 Module = 100,
175 Staff = 101,
176 Room = 102,
177 Group = 103,
178 Student = 104,
179 Team = 105,
180 Equipment = 106,
181 Course = 107,
182}
183
184mod private {
185 pub trait Sealed {}
189}
190
191#[cfg(test)]
192mod tests {
193 use super::*;
194 use serde_json::{from_value, json, to_value};
195
196 #[test]
197 fn serialize_entity_type() {
198 assert_eq!(to_value(Unknown).unwrap(), json!(0));
199 assert_eq!(to_value(Student).unwrap(), json!(104));
200 }
201
202 #[test]
203 fn deserialize_entity_type() {
204 from_value::<Unknown>(json!(0)).unwrap();
205 from_value::<Group>(json!(103)).unwrap();
206 from_value::<Unknown>(json!(100)).unwrap_err();
207 from_value::<Staff>(json!(null)).unwrap_err();
208 }
209
210 #[test]
211 fn serialize_unknown_id() {
212 assert_eq!(to_value(UnknownId).unwrap(), json!(null));
213 }
214
215 #[test]
216 fn deserialize_unknown_id() {
217 from_value::<UnknownId>(json!(null)).unwrap();
218 from_value::<UnknownId>(json!(100)).unwrap_err();
219 }
220
221 #[test]
222 fn serialize_room_id() {
223 assert_eq!(
224 to_value(RoomId("1173077".to_owned())).unwrap(),
225 json!("1173077")
226 );
227 }
228
229 #[test]
230 fn deserialize_room_id() {
231 assert_eq!(
232 from_value::<RoomId>(json!("1172947")).unwrap(),
233 RoomId("1172947".to_owned())
234 );
235 from_value::<RoomId>(json!(1172976)).unwrap_err();
236 }
237}