1#[cfg(feature = "bson")]
2mod bson;
3pub mod oauth;
4
5use core::fmt;
6use derive_more::{Display, Error, FromStr};
7use serde::{
8 de::{self, Visitor},
9 Deserialize, Deserializer, Serialize,
10};
11use serde_json::Value;
12use std::{collections::HashMap, str::FromStr, time::SystemTime};
13use ts_rs::TS;
14use uuid::Uuid;
15
16const APP_NAME: &str = "NetsBlox";
17
18#[derive(Deserialize, Serialize, TS)]
19#[serde(rename_all = "camelCase")]
20#[ts(export)]
21pub struct ClientConfig {
22 pub client_id: String,
23 #[ts(optional)]
24 pub username: Option<String>,
25 pub services_hosts: Vec<ServiceHost>,
26 pub cloud_url: String,
27}
28
29#[derive(Deserialize, Serialize, TS)]
30#[ts(export)]
31pub struct InvitationResponse {
32 pub response: FriendLinkState,
33}
34
35#[derive(Serialize, Deserialize, Clone, Debug, TS)]
36#[serde(rename_all = "camelCase")]
37#[ts(export)]
38pub struct User {
39 pub username: String,
40 pub email: String,
41 #[ts(optional)]
42 pub group_id: Option<GroupId>,
43 pub role: UserRole,
44 #[ts(skip)]
45 pub created_at: SystemTime,
46 pub linked_accounts: Vec<LinkedAccount>,
47 #[ts(optional)]
48 pub services_hosts: Option<Vec<ServiceHost>>,
49}
50
51#[derive(Serialize, Deserialize, TS, Clone)]
52#[serde(rename_all = "camelCase")]
53#[ts(export)]
54pub struct UpdateUserData {
55 pub email: Option<String>,
56 pub group_id: Option<GroupId>,
57 pub role: Option<UserRole>,
58}
59
60#[derive(Serialize, Deserialize, Debug, TS)]
61#[serde(rename_all = "camelCase")]
62#[ts(export)]
63pub struct NewUser {
64 pub username: String,
65 pub email: String,
66 #[ts(optional)]
67 pub password: Option<String>,
68 #[ts(optional)]
69 pub group_id: Option<GroupId>,
70 #[ts(optional)]
71 pub role: Option<UserRole>,
72}
73
74#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, TS)]
75#[serde(rename_all = "camelCase")]
76#[ts(export)]
77pub enum UserRole {
78 User,
79 Teacher,
80 Moderator,
81 Admin,
82}
83
84#[derive(Deserialize, Serialize, Clone, Debug, TS)]
85#[serde(rename_all = "camelCase")]
86#[ts(export)]
87pub struct NetworkTraceMetadata {
88 pub id: String,
89 #[ts(type = "any")] pub start_time: SystemTime,
91 #[ts(type = "any | null")] #[ts(optional)]
93 pub end_time: Option<SystemTime>,
94}
95
96#[derive(Deserialize, Serialize, Debug, Clone, TS)]
97#[serde(rename_all = "camelCase")]
98#[ts(export)]
99pub struct SentMessage {
100 pub project_id: ProjectId,
101 pub recipients: Vec<ClientState>,
102 #[ts(type = "any")] pub time: SystemTime,
104 pub source: ClientState,
105
106 #[ts(type = "any")]
107 pub content: serde_json::Value,
108}
109
110#[derive(TS, Deserialize, Serialize, Debug, Clone)]
111#[serde(rename_all = "camelCase")]
112#[ts(export)]
113pub struct OccupantInvite {
114 pub username: String,
115 pub project_id: ProjectId,
116 pub role_id: RoleId,
117 #[ts(type = "any")] pub created_at: SystemTime,
119}
120
121#[derive(Debug, Display, Error, TS)]
122#[display(fmt = "Unable to parse user role. Expected admin, moderator, or user.")]
123#[ts(export)]
124pub struct UserRoleError;
125
126impl FromStr for UserRole {
127 type Err = UserRoleError;
128 fn from_str(s: &str) -> Result<Self, Self::Err> {
129 match s {
130 "admin" => Ok(UserRole::Admin),
131 "moderator" => Ok(UserRole::Moderator),
132 "teacher" => Ok(UserRole::Teacher),
133 "user" => Ok(UserRole::User),
134 _ => Err(UserRoleError),
135 }
136 }
137}
138
139#[derive(Serialize, Deserialize, Clone, Debug, TS)]
140#[serde(rename_all = "camelCase")]
141#[ts(export)]
142pub struct ServiceHost {
143 pub url: String,
144 pub categories: Vec<String>,
145}
146
147#[derive(Serialize, Deserialize, Clone, Debug, TS)]
148#[ts(export)]
149pub struct LinkedAccount {
150 pub username: String,
151 pub strategy: String,
152}
153
154#[derive(TS, Serialize, Deserialize, Clone)]
155#[serde(rename_all = "camelCase")]
156#[ts(export)]
157pub struct BannedAccount {
158 pub username: String,
159 pub email: String,
160 #[ts(type = "any")] pub banned_at: SystemTime,
162}
163
164#[derive(Serialize, Deserialize, Debug, TS)]
165#[serde(rename_all = "camelCase")]
166#[ts(export)]
167pub struct LoginRequest {
168 pub credentials: Credentials,
169 #[ts(optional)]
170 pub client_id: Option<ClientId>, }
172
173#[derive(Deserialize, Serialize, Debug, Clone, TS)]
174#[ts(export)]
175pub enum Credentials {
176 Snap { username: String, password: String },
177 NetsBlox { username: String, password: String },
178}
179
180impl From<Credentials> for LinkedAccount {
181 fn from(creds: Credentials) -> LinkedAccount {
182 match creds {
183 Credentials::Snap { username, .. } => LinkedAccount {
184 username,
185 strategy: "snap".to_owned(),
186 },
187 Credentials::NetsBlox { username, .. } => LinkedAccount {
188 username,
190 strategy: "netsblox".to_owned(),
191 },
192 }
193 }
194}
195
196pub type FriendLinkId = String; #[derive(TS, Deserialize, Serialize, Clone, Debug)]
198#[serde(rename_all = "camelCase")]
199#[ts(export)]
200pub struct FriendLink {
201 pub id: FriendLinkId,
202 pub sender: String,
203 pub recipient: String,
204 pub state: FriendLinkState,
205 #[ts(type = "any")] pub created_at: SystemTime,
207 #[ts(type = "any")] pub updated_at: SystemTime,
209}
210
211#[derive(Deserialize, Serialize, Clone, Debug, TS)]
212#[ts(export)]
213pub enum FriendLinkState {
214 Pending,
215 Approved,
216 Rejected,
217 Blocked,
218}
219
220#[derive(Debug)]
221pub struct ParseFriendLinkStateError;
222
223impl fmt::Display for ParseFriendLinkStateError {
224 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
225 write!(f, "invalid friend link state")
226 }
227}
228
229impl FromStr for FriendLinkState {
230 type Err = ParseFriendLinkStateError;
231
232 fn from_str(s: &str) -> Result<Self, Self::Err> {
233 match s {
234 "pending" => Ok(FriendLinkState::Pending),
235 "approved" => Ok(FriendLinkState::Approved),
236 "rejected" => Ok(FriendLinkState::Rejected),
237 "blocked" => Ok(FriendLinkState::Blocked),
238 _ => Err(ParseFriendLinkStateError),
239 }
240 }
241}
242
243#[derive(Serialize, Deserialize, Clone, Debug, TS)]
244#[serde(rename_all = "camelCase")]
245#[ts(export)]
246pub struct FriendInvite {
247 pub id: String,
248 pub sender: String,
249 pub recipient: String,
250 #[ts(type = "any")] pub created_at: SystemTime,
252}
253
254#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Display, Hash, TS)]
255#[ts(export)]
256pub struct ProjectId(String);
257
258impl ProjectId {
259 pub fn new(id: String) -> Self {
260 ProjectId(id)
261 }
262}
263
264#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Display, Hash, TS)]
265#[ts(export)]
266pub struct RoleId(String);
267
268impl RoleId {
269 pub fn new(id: String) -> Self {
270 RoleId(id)
271 }
272
273 pub fn as_str(&self) -> &str {
274 &self.0
275 }
276}
277
278#[derive(Deserialize, Serialize, Clone, Debug, TS)]
279#[serde(rename_all = "camelCase")]
280#[ts(export)]
281pub struct ProjectMetadata {
282 pub id: ProjectId,
283 pub owner: String,
284 pub name: String,
285 #[ts(type = "any")] pub updated: SystemTime,
287 pub state: PublishState,
288 pub collaborators: std::vec::Vec<String>,
289 pub network_traces: Vec<NetworkTraceMetadata>,
290 #[ts(type = "any")] pub origin_time: SystemTime,
292 pub save_state: SaveState,
293 pub roles: HashMap<RoleId, RoleMetadata>,
294}
295
296#[derive(Deserialize, Serialize, Clone, Debug, TS)]
297#[ts(export)]
298pub enum SaveState {
299 Created,
300 Transient,
301 Broken,
302 Saved,
303}
304
305#[derive(Deserialize, Serialize, Clone, Debug, TS)]
306#[ts(export)]
307pub struct RoleMetadata {
308 pub name: String,
309 pub code: String,
310 pub media: String,
311}
312
313#[derive(Deserialize, Serialize, TS)]
314#[serde(rename_all = "camelCase")]
315#[ts(export)]
316pub struct Project {
317 pub id: ProjectId,
318 pub owner: String,
319 pub name: String,
320 #[ts(type = "any")] pub updated: SystemTime,
322 pub state: PublishState,
323 pub collaborators: std::vec::Vec<String>,
324 #[ts(type = "any")] pub origin_time: SystemTime,
326 pub save_state: SaveState,
327 pub roles: HashMap<RoleId, RoleData>,
328}
329
330impl Project {
331 pub fn to_xml(&self) -> String {
332 let role_str: String = self
333 .roles
334 .values()
335 .map(|role| role.to_xml())
336 .collect::<Vec<_>>()
337 .join(" ");
338 format!(
339 "<room name=\"{}\" app=\"{}\">{}</room>",
340 self.name, APP_NAME, role_str
341 )
342 }
343}
344
345#[derive(Deserialize, Serialize, TS)]
346#[serde(rename_all = "camelCase")]
347#[ts(export)]
348pub struct RoleDataResponse {
349 pub id: Uuid,
350 pub data: RoleData,
351}
352
353#[derive(Deserialize, Serialize, Debug, Clone, TS)]
354#[ts(export)]
355pub struct RoleData {
356 pub name: String,
357 pub code: String,
358 pub media: String,
359}
360
361impl RoleData {
362 pub fn to_xml(&self) -> String {
363 let name = self.name.replace('\"', "\\\"");
364 format!("<role name=\"{}\">{}{}</role>", name, self.code, self.media)
365 }
366}
367
368#[derive(Deserialize, Serialize, TS)]
369#[serde(rename_all = "camelCase")]
370#[ts(export)]
371pub struct ClientStateData {
372 pub state: ClientState,
373}
374
375#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, TS)]
376#[serde(rename_all = "camelCase")]
377#[ts(export)]
378pub enum ClientState {
379 Browser(BrowserClientState),
380 External(ExternalClientState),
381}
382
383#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, TS)]
384#[serde(rename_all = "camelCase")]
385#[ts(export)]
386pub struct BrowserClientState {
387 pub role_id: RoleId,
388 pub project_id: ProjectId,
389}
390
391#[derive(Debug, Serialize, Clone, Hash, Eq, PartialEq, TS)]
392#[ts(export)]
393pub struct AppId(String);
394
395impl AppId {
396 pub fn new(addr: &str) -> Self {
397 Self(addr.to_lowercase())
398 }
399
400 pub fn as_str(&self) -> &str {
401 &self.0
402 }
403}
404
405impl<'de> Deserialize<'de> for AppId {
406 fn deserialize<D>(deserializer: D) -> Result<AppId, D::Error>
407 where
408 D: Deserializer<'de>,
409 {
410 let value = Value::deserialize(deserializer)?;
411 if let Value::String(s) = value {
412 Ok(AppId::new(s.as_str()))
413 } else {
414 Err(de::Error::custom("Invalid App ID expected a string"))
415 }
416 }
417}
418
419struct AppIdVisitor;
420impl<'de> Visitor<'de> for AppIdVisitor {
421 type Value = AppId;
422
423 fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
424 formatter.write_str("an App ID string")
425 }
426
427 fn visit_str<E>(self, value: &str) -> Result<Self::Value, E> {
428 println!("deserializing {}", value);
429 Ok(AppId::new(value))
430 }
431
432 fn visit_string<E>(self, value: String) -> Result<Self::Value, E> {
433 println!("deserializing {}", value);
434 Ok(AppId::new(value.as_str()))
435 }
436}
437
438#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Eq, TS)]
439#[serde(rename_all = "camelCase")]
440#[ts(export)]
441pub struct ExternalClientState {
442 pub address: String,
443 pub app_id: AppId,
444}
445
446#[derive(Serialize, Deserialize, TS)]
447#[ts(export)]
448pub struct CreateLibraryData {
449 pub name: String,
450 pub notes: String,
451 pub blocks: String,
452}
453
454#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord, TS)]
455#[ts(export)]
456pub enum PublishState {
457 Private,
458 ApprovalDenied,
459 PendingApproval,
460 Public,
461}
462
463#[derive(Serialize, Deserialize, Clone, Debug, TS)]
464#[ts(export)]
465pub struct LibraryMetadata {
466 pub owner: String,
467 pub name: String,
468 pub notes: String,
469 pub state: PublishState,
470}
471
472impl LibraryMetadata {
473 pub fn new(
474 owner: String,
475 name: String,
476 state: PublishState,
477 notes: Option<String>,
478 ) -> LibraryMetadata {
479 LibraryMetadata {
480 owner,
481 name,
482 notes: notes.unwrap_or_default(),
483 state,
484 }
485 }
486}
487
488#[derive(Serialize, Deserialize, Clone, TS)]
489#[serde(rename_all = "camelCase")]
490#[ts(export)]
491pub struct CreateGroupData {
492 pub name: String,
493 #[ts(optional)]
494 pub services_hosts: Option<Vec<ServiceHost>>,
495}
496
497#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Display, Hash, FromStr, TS)]
498#[ts(export)]
499pub struct GroupId(String);
500
501impl GroupId {
502 pub fn new(name: String) -> Self {
503 Self(name)
504 }
505
506 pub fn as_str(&self) -> &str {
507 &self.0
508 }
509}
510
511#[derive(Serialize, Deserialize, Clone, Debug, TS)]
512#[serde(rename_all = "camelCase")]
513#[ts(export)]
514pub struct Group {
515 pub id: GroupId,
516 pub owner: String,
517 pub name: String,
518 #[ts(optional)]
519 pub services_hosts: Option<Vec<ServiceHost>>,
520}
521
522#[derive(Serialize, Deserialize, TS)]
523#[ts(export)]
524pub struct UpdateGroupData {
525 pub name: String,
526}
527
528#[derive(Deserialize, Serialize, Clone, Debug, TS)]
529#[ts(export)]
530pub enum InvitationState {
531 Pending,
532 Accepted,
533 Rejected,
534}
535
536pub type InvitationId = String;
537
538#[derive(TS, Deserialize, Serialize, Clone, Debug)]
539#[serde(rename_all = "camelCase")]
540#[ts(export)]
541pub struct CollaborationInvite {
542 pub id: String,
543 pub sender: String,
544 pub receiver: String,
545 pub project_id: ProjectId,
546 pub state: InvitationState,
547 #[ts(type = "any")] pub created_at: SystemTime,
549}
550
551impl CollaborationInvite {
552 pub fn new(sender: String, receiver: String, project_id: ProjectId) -> Self {
553 CollaborationInvite {
554 id: Uuid::new_v4().to_string(),
555 sender,
556 receiver,
557 project_id,
558 state: InvitationState::Pending,
559 created_at: SystemTime::now(),
560 }
561 }
562}
563
564#[derive(Deserialize, Serialize, TS)]
565#[serde(rename_all = "camelCase")]
566#[ts(export)]
567pub struct UpdateProjectData {
568 pub name: String,
569 #[ts(optional)]
570 pub client_id: Option<ClientId>,
571}
572
573#[derive(Deserialize, Serialize, Debug, TS)]
574#[serde(rename_all = "camelCase")]
575#[ts(export)]
576pub struct UpdateRoleData {
577 pub name: String,
578 #[ts(optional)]
579 pub client_id: Option<ClientId>,
580}
581
582#[derive(Deserialize, Serialize, TS)]
583#[serde(rename_all = "camelCase")]
584#[ts(export)]
585pub struct CreateProjectData {
586 #[ts(optional)]
587 pub owner: Option<String>,
588 pub name: String,
589 #[ts(optional)]
590 pub roles: Option<Vec<RoleData>>,
591 #[ts(optional)]
592 pub client_id: Option<ClientId>,
593 #[ts(optional)]
594 pub save_state: Option<SaveState>,
595}
596
597#[derive(Deserialize, Serialize, Debug, Clone, PartialEq, Eq, Hash, TS)]
599#[ts(export)]
600pub struct ClientId(String);
601
602impl ClientId {
603 pub fn new(addr: String) -> Self {
604 Self(addr)
605 }
606
607 pub fn as_str(&self) -> &str {
608 &self.0
609 }
610}
611
612#[derive(Debug, Display, Error, TS)]
613#[display(fmt = "Invalid client ID. Must start with a _")]
614#[ts(export)]
615pub struct ClientIDError;
616
617impl FromStr for ClientId {
618 type Err = ClientIDError;
619 fn from_str(s: &str) -> Result<Self, Self::Err> {
620 if s.starts_with('_') {
621 Ok(ClientId::new(s.to_owned()))
622 } else {
623 Err(ClientIDError)
624 }
625 }
626}
627
628#[derive(Deserialize, Serialize, Debug, TS)]
629#[serde(rename_all = "camelCase")]
630#[ts(export)]
631pub struct ExternalClient {
632 #[ts(optional)]
633 pub username: Option<String>,
634 pub address: String,
635 pub app_id: AppId,
636}
637
638#[derive(Deserialize, Serialize, Clone, Debug, TS)]
639#[ts(export)]
640pub struct RoomState {
641 pub id: ProjectId,
642 pub owner: String,
643 pub name: String,
644 pub roles: HashMap<RoleId, RoleState>,
645 pub collaborators: Vec<String>,
646 pub version: u64,
647}
648
649#[derive(Deserialize, Serialize, Clone, Debug, TS)]
650#[ts(export)]
651pub struct RoleState {
652 pub name: String,
653 pub occupants: Vec<OccupantState>,
654}
655
656#[derive(Deserialize, Serialize, Clone, Debug, TS)]
657#[ts(export)]
658pub struct OccupantState {
659 pub id: ClientId,
660 pub name: String,
661}
662
663#[derive(Deserialize, Serialize, Debug, TS)]
664#[serde(rename_all = "camelCase")]
665#[ts(export)]
666pub struct OccupantInviteData {
667 pub username: String,
668 pub role_id: RoleId,
669 #[ts(optional)]
670 pub sender: Option<String>,
671}
672
673#[derive(Deserialize, Serialize, Debug, Clone, TS)]
674#[serde(rename_all = "camelCase")]
675#[ts(export)]
676pub struct AuthorizedServiceHost {
677 pub url: String,
678 pub id: String,
679 pub visibility: ServiceHostScope,
680}
681
682#[derive(Deserialize, Serialize, Debug, Clone, TS)]
683#[serde(rename_all = "camelCase")]
684#[ts(export)]
685pub enum ServiceHostScope {
686 Public(Vec<String>),
687 Private,
688}
689
690#[derive(Deserialize, Serialize, Debug, Clone, TS)]
691#[serde(rename_all = "camelCase")]
692#[ts(export)]
693pub struct ClientInfo {
694 #[ts(optional)]
695 pub username: Option<String>,
696 #[ts(optional)]
697 pub state: Option<ClientState>,
698}
699
700#[derive(Deserialize, Serialize, Debug, Clone, TS)]
702#[ts(export)]
703pub struct ServiceSettings {
704 #[ts(optional)]
706 pub user: Option<String>,
707 #[ts(optional)]
709 pub member: Option<String>,
710 pub groups: HashMap<GroupId, String>,
712}
713
714#[derive(Deserialize, Serialize, Debug, Clone, TS)]
716#[serde(rename_all = "camelCase")]
717#[ts(export)]
718pub struct SendMessage {
719 pub sender: Option<SendMessageSender>,
720 pub target: SendMessageTarget,
721 #[ts(type = "object")]
723 pub content: Value,
724}
725
726#[derive(Deserialize, Serialize, Debug, Clone, TS)]
728#[serde(rename_all = "camelCase")]
729#[ts(export)]
730pub struct LogMessage {
731 pub sender: String,
732 pub recipients: Vec<String>,
733 #[ts(type = "object")]
735 pub content: Value,
736}
737
738#[derive(Deserialize, Serialize, Debug, Clone, TS)]
739#[serde(rename_all = "camelCase")]
740#[ts(export)]
741pub enum SendMessageSender {
742 Username(String),
743 Client(ClientId),
744}
745
746#[derive(Deserialize, Serialize, Debug, Clone, TS)]
747#[serde(rename_all = "camelCase")]
748#[ts(export)]
749pub enum SendMessageTarget {
750 Address {
751 address: String,
752 },
753 #[serde(rename_all = "camelCase")]
754 Room {
755 project_id: ProjectId,
756 },
757 #[serde(rename_all = "camelCase")]
758 Role {
759 project_id: ProjectId,
760 role_id: RoleId,
761 },
762 #[serde(rename_all = "camelCase")]
763 Client {
764 #[ts(optional)]
765 state: Option<ClientState>,
766 client_id: ClientId,
767 },
768}
769
770#[derive(Serialize, Deserialize, Clone, Debug, TS)]
771#[ts(export)]
772pub struct MagicLinkId(String);
773
774impl MagicLinkId {
775 pub fn new(id: String) -> Self {
776 Self(id)
777 }
778
779 pub fn as_str(&self) -> &str {
780 &self.0
781 }
782}
783
784#[derive(Serialize, Deserialize, Clone, Debug, TS)]
785#[serde(rename_all = "camelCase")]
786#[ts(export)]
787pub struct MagicLinkLoginData {
788 pub link_id: MagicLinkId,
789 pub username: String,
790 #[ts(optional)]
791 pub client_id: Option<ClientId>,
792 #[ts(optional)]
793 pub redirect_uri: Option<String>,
794}
795
796#[derive(Serialize, Deserialize, Clone, Debug, TS)]
797#[serde(rename_all = "camelCase")]
798#[ts(export)]
799pub struct CreateMagicLinkData {
800 pub email: String,
801 #[ts(optional)]
802 pub redirect_uri: Option<String>,
803}
804
805#[cfg(test)]
806mod tests {
807 use super::*;
808 use uuid::Uuid;
809
810 #[test]
811 fn deserialize_project_id() {
812 let project_id_str = &format!("\"{}\"", Uuid::new_v4());
813 let _project_id: ProjectId = serde_json::from_str(project_id_str)
814 .unwrap_or_else(|_err| panic!("Unable to parse ProjectId from {}", project_id_str));
815 }
816
817 #[test]
818 fn deserialize_role_id() {
819 let role_id_str = &format!("\"{}\"", Uuid::new_v4());
820 let _role_id: RoleId = serde_json::from_str(role_id_str)
821 .unwrap_or_else(|_err| panic!("Unable to parse RoleId from {}", role_id_str));
822 }
823
824 #[test]
825 fn should_compare_roles() {
826 assert!(UserRole::Teacher > UserRole::User);
827 assert!(UserRole::Moderator > UserRole::User);
828 assert!(UserRole::Admin > UserRole::User);
829
830 assert!(UserRole::Moderator > UserRole::Teacher);
831 assert!(UserRole::Admin > UserRole::Teacher);
832
833 assert!(UserRole::Admin > UserRole::Moderator);
834
835 assert!(UserRole::User == UserRole::User);
836 assert!(UserRole::Teacher == UserRole::Teacher);
837 assert!(UserRole::Moderator == UserRole::Moderator);
838 assert!(UserRole::Admin == UserRole::Admin);
839 }
840
841 #[test]
842 fn serialize_userroles_as_strings() {
843 let role_str = serde_json::to_string(&UserRole::User).unwrap();
844 assert_eq!(&role_str, "\"user\"");
845 }
846
847 #[test]
848 fn deserialize_app_id_lowercase() {
849 let app_id_str = String::from("\"NetsBlox\"");
850 let app_id: AppId = serde_json::from_str(&app_id_str).unwrap();
851 assert_eq!(&app_id.as_str(), &"netsblox");
852 assert_eq!(app_id, AppId::new("netsblox"));
853 }
854
855 #[test]
856 fn publish_state_priv_lt_pending() {
857 assert!(PublishState::Private < PublishState::PendingApproval);
858 }
859
860 #[test]
861 fn publish_state_pending_lt_public() {
862 assert!(PublishState::PendingApproval < PublishState::Public);
863 }
864
865 #[test]
866 fn publish_state_public_eq() {
867 assert!(PublishState::Public == PublishState::Public);
868 }
869}