use serde::{Deserialize, Serialize};
use crate::error::{Error, Result};
use crate::message::{Message, MessageType};
use crate::room::Role;
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(tag = "op", rename_all = "lowercase")]
pub enum ControlOp {
Grant {
name: String,
role: Role,
#[serde(default, skip_serializing_if = "Option::is_none")]
until: Option<String>,
},
Pause,
Resume,
Mute {
name: String,
},
Unmute {
name: String,
},
Revoke {
name: String,
},
Hold {
on: bool,
},
Rotated {
new_room_id: String,
},
}
impl ControlOp {
pub fn from_message(msg: &Message) -> Result<ControlOp> {
if msg.kind != MessageType::Control {
return Err(Error::Invalid("not a control message".into()));
}
serde_json::from_value(msg.data.clone())
.map_err(|e| Error::Invalid(format!("bad control message: {e}")))
}
pub fn describe(&self) -> String {
match self {
ControlOp::Grant { name, role, until } => match until {
Some(u) => format!("{name} is now {role} until {u}"),
None => format!("{name} is now {role}"),
},
ControlOp::Pause => "room paused".into(),
ControlOp::Resume => "room resumed".into(),
ControlOp::Mute { name } => format!("{name} muted"),
ControlOp::Unmute { name } => format!("{name} unmuted"),
ControlOp::Revoke { name } => format!("{name} revoked"),
ControlOp::Hold { on } => {
if *on {
"legal hold on".into()
} else {
"legal hold off".into()
}
}
ControlOp::Rotated { new_room_id } => format!("room rotated to {new_room_id}"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip() {
let op = ControlOp::Grant {
name: "bob".into(),
role: Role::Approver,
until: Some("2026-12-31T00:00:00Z".into()),
};
let v = serde_json::to_value(&op).unwrap();
assert_eq!(v["op"], "grant");
assert_eq!(v["role"], "approver");
let back: ControlOp = serde_json::from_value(v).unwrap();
assert_eq!(back, op);
assert_eq!(ControlOp::Pause.describe(), "room paused");
}
}