use serde_json::Value;
pub(super) fn validate_payload(json: &str) -> Result<String, String> {
let value: Value = serde_json::from_str(json).map_err(|e| format!("invalid JSON: {e}"))?;
let obj = value
.as_object()
.ok_or_else(|| "JSON must be an object with a \"cmd\" field".to_string())?;
if !obj.contains_key("cmd") {
return Err("JSON object must include a \"cmd\" field".to_string());
}
Ok(value.to_string())
}
pub(super) fn reply_ok(reply: &Value) -> bool {
reply.get("ok").and_then(Value::as_bool).unwrap_or(false)
}
#[derive(Clone, Copy, Debug)]
pub enum WatchTarget {
Camera,
State,
Streaming,
Profile,
}
impl WatchTarget {
pub(super) fn cmd(self) -> &'static str {
match self {
WatchTarget::Camera => "camera-get",
WatchTarget::State => "state",
WatchTarget::Streaming => "streaming",
WatchTarget::Profile => "profile",
}
}
pub(super) fn label(self) -> &'static str {
match self {
WatchTarget::Camera => "camera",
WatchTarget::State => "state",
WatchTarget::Streaming => "streaming",
WatchTarget::Profile => "profile",
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn validate_payload_accepts_object_with_cmd() {
let out = validate_payload(r#"{"cmd":"state"}"#).expect("valid payload");
let value: Value = serde_json::from_str(&out).unwrap();
assert_eq!(value.get("cmd").and_then(Value::as_str), Some("state"));
}
#[test]
fn validate_payload_preserves_extra_fields() {
let out = validate_payload(r#"{"cmd":"decal-remove","id":7}"#).expect("valid payload");
let value: Value = serde_json::from_str(&out).unwrap();
assert_eq!(value.get("id").and_then(Value::as_u64), Some(7));
}
#[test]
fn validate_payload_rejects_invalid_json() {
assert!(validate_payload("{not json").is_err());
}
#[test]
fn validate_payload_rejects_non_object() {
assert!(validate_payload(r#"["cmd","state"]"#).is_err());
assert!(validate_payload(r#""state""#).is_err());
}
#[test]
fn validate_payload_rejects_missing_cmd() {
assert!(validate_payload(r#"{"id":1}"#).is_err());
}
#[test]
fn reply_ok_reads_the_ok_field() {
assert!(reply_ok(&serde_json::json!({ "ok": true })));
assert!(!reply_ok(&serde_json::json!({ "ok": false })));
assert!(!reply_ok(&serde_json::json!({ "pong": true })));
}
#[test]
fn watch_target_maps_to_server_commands() {
assert_eq!(WatchTarget::Camera.cmd(), "camera-get");
assert_eq!(WatchTarget::State.cmd(), "state");
assert_eq!(WatchTarget::Streaming.cmd(), "streaming");
assert_eq!(WatchTarget::Profile.cmd(), "profile");
assert_eq!(WatchTarget::Camera.label(), "camera");
assert_eq!(WatchTarget::Profile.label(), "profile");
}
}