concinnity_dev/debug/
protocol.rs1use serde_json::Value;
9
10pub(super) fn validate_payload(json: &str) -> Result<String, String> {
13 let value: Value = serde_json::from_str(json).map_err(|e| format!("invalid JSON: {e}"))?;
14 let obj = value
15 .as_object()
16 .ok_or_else(|| "JSON must be an object with a \"cmd\" field".to_string())?;
17 if !obj.contains_key("cmd") {
18 return Err("JSON object must include a \"cmd\" field".to_string());
19 }
20 Ok(value.to_string())
21}
22
23pub(super) fn reply_ok(reply: &Value) -> bool {
25 reply.get("ok").and_then(Value::as_bool).unwrap_or(false)
26}
27
28#[derive(Clone, Copy, Debug)]
32pub enum WatchTarget {
33 Camera,
35 State,
37 Streaming,
39 Profile,
41}
42
43impl WatchTarget {
44 pub(super) fn cmd(self) -> &'static str {
45 match self {
46 WatchTarget::Camera => "camera-get",
47 WatchTarget::State => "state",
48 WatchTarget::Streaming => "streaming",
49 WatchTarget::Profile => "profile",
50 }
51 }
52
53 pub(super) fn label(self) -> &'static str {
54 match self {
55 WatchTarget::Camera => "camera",
56 WatchTarget::State => "state",
57 WatchTarget::Streaming => "streaming",
58 WatchTarget::Profile => "profile",
59 }
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 #[test]
68 fn validate_payload_accepts_object_with_cmd() {
69 let out = validate_payload(r#"{"cmd":"state"}"#).expect("valid payload");
70 let value: Value = serde_json::from_str(&out).unwrap();
71 assert_eq!(value.get("cmd").and_then(Value::as_str), Some("state"));
72 }
73
74 #[test]
75 fn validate_payload_preserves_extra_fields() {
76 let out = validate_payload(r#"{"cmd":"decal-remove","id":7}"#).expect("valid payload");
77 let value: Value = serde_json::from_str(&out).unwrap();
78 assert_eq!(value.get("id").and_then(Value::as_u64), Some(7));
79 }
80
81 #[test]
82 fn validate_payload_rejects_invalid_json() {
83 assert!(validate_payload("{not json").is_err());
84 }
85
86 #[test]
87 fn validate_payload_rejects_non_object() {
88 assert!(validate_payload(r#"["cmd","state"]"#).is_err());
89 assert!(validate_payload(r#""state""#).is_err());
90 }
91
92 #[test]
93 fn validate_payload_rejects_missing_cmd() {
94 assert!(validate_payload(r#"{"id":1}"#).is_err());
95 }
96
97 #[test]
98 fn reply_ok_reads_the_ok_field() {
99 assert!(reply_ok(&serde_json::json!({ "ok": true })));
100 assert!(!reply_ok(&serde_json::json!({ "ok": false })));
101 assert!(!reply_ok(&serde_json::json!({ "pong": true })));
102 }
103
104 #[test]
105 fn watch_target_maps_to_server_commands() {
106 assert_eq!(WatchTarget::Camera.cmd(), "camera-get");
107 assert_eq!(WatchTarget::State.cmd(), "state");
108 assert_eq!(WatchTarget::Streaming.cmd(), "streaming");
109 assert_eq!(WatchTarget::Profile.cmd(), "profile");
110 assert_eq!(WatchTarget::Camera.label(), "camera");
111 assert_eq!(WatchTarget::Profile.label(), "profile");
112 }
113}