1use serde::{Deserialize, Serialize};
5
6use crate::error::{Error, Result};
7use crate::message::{Message, MessageType};
8use crate::room::Role;
9
10#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(tag = "op", rename_all = "lowercase")]
12pub enum ControlOp {
13 Grant {
15 name: String,
16 role: Role,
17 #[serde(default, skip_serializing_if = "Option::is_none")]
18 until: Option<String>,
19 },
20 Pause,
22 Resume,
23 Mute {
25 name: String,
26 },
27 Unmute {
28 name: String,
29 },
30 Revoke {
32 name: String,
33 },
34 Hold {
36 on: bool,
37 },
38 Rotated {
40 new_room_id: String,
41 },
42}
43
44impl ControlOp {
45 pub fn from_message(msg: &Message) -> Result<ControlOp> {
47 if msg.kind != MessageType::Control {
48 return Err(Error::Invalid("not a control message".into()));
49 }
50 serde_json::from_value(msg.data.clone())
51 .map_err(|e| Error::Invalid(format!("bad control message: {e}")))
52 }
53
54 pub fn describe(&self) -> String {
56 match self {
57 ControlOp::Grant { name, role, until } => match until {
58 Some(u) => format!("{name} is now {role} until {u}"),
59 None => format!("{name} is now {role}"),
60 },
61 ControlOp::Pause => "room paused".into(),
62 ControlOp::Resume => "room resumed".into(),
63 ControlOp::Mute { name } => format!("{name} muted"),
64 ControlOp::Unmute { name } => format!("{name} unmuted"),
65 ControlOp::Revoke { name } => format!("{name} revoked"),
66 ControlOp::Hold { on } => {
67 if *on {
68 "legal hold on".into()
69 } else {
70 "legal hold off".into()
71 }
72 }
73 ControlOp::Rotated { new_room_id } => format!("room rotated to {new_room_id}"),
74 }
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn roundtrip() {
84 let op = ControlOp::Grant {
85 name: "bob".into(),
86 role: Role::Approver,
87 until: Some("2026-12-31T00:00:00Z".into()),
88 };
89 let v = serde_json::to_value(&op).unwrap();
90 assert_eq!(v["op"], "grant");
91 assert_eq!(v["role"], "approver");
92 let back: ControlOp = serde_json::from_value(v).unwrap();
93 assert_eq!(back, op);
94 assert_eq!(ControlOp::Pause.describe(), "room paused");
95 }
96}