use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ZcCommand {
pub cmd: String,
#[serde(default)]
pub params: serde_json::Value,
}
impl ZcCommand {
pub fn new(cmd: impl Into<String>, params: serde_json::Value) -> Self {
Self {
cmd: cmd.into(),
params,
}
}
pub fn simple(cmd: impl Into<String>) -> Self {
Self {
cmd: cmd.into(),
params: serde_json::Value::Object(serde_json::Map::new()),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ZcResponse {
pub ok: bool,
#[serde(default)]
pub data: serde_json::Value,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
impl ZcResponse {
pub fn success(data: serde_json::Value) -> Self {
Self {
ok: true,
data,
error: None,
}
}
pub fn error(message: impl Into<String>) -> Self {
Self {
ok: false,
data: serde_json::Value::Null,
error: Some(message.into()),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
#[test]
fn zc_command_serialization_roundtrip() {
let cmd = ZcCommand::new("gpio_write", json!({"pin": 25, "value": 1}));
let json = serde_json::to_string(&cmd).unwrap();
let parsed: ZcCommand = serde_json::from_str(&json).unwrap();
assert_eq!(parsed.cmd, "gpio_write");
assert_eq!(parsed.params["pin"], 25);
assert_eq!(parsed.params["value"], 1);
}
#[test]
fn zc_command_simple_has_empty_params() {
let cmd = ZcCommand::simple("ping");
assert_eq!(cmd.cmd, "ping");
assert!(cmd.params.is_object());
}
#[test]
fn zc_response_success_roundtrip() {
let resp = ZcResponse::success(json!({"value": 1}));
let json = serde_json::to_string(&resp).unwrap();
let parsed: ZcResponse = serde_json::from_str(&json).unwrap();
assert!(parsed.ok);
assert_eq!(parsed.data["value"], 1);
assert!(parsed.error.is_none());
}
#[test]
fn zc_response_error_roundtrip() {
let resp = ZcResponse::error("pin not available");
let json = serde_json::to_string(&resp).unwrap();
let parsed: ZcResponse = serde_json::from_str(&json).unwrap();
assert!(!parsed.ok);
assert_eq!(parsed.error.as_deref(), Some("pin not available"));
}
#[test]
fn zc_command_wire_format_matches_spec() {
let cmd = ZcCommand::new("gpio_write", json!({"pin": 25, "value": 1}));
let v: serde_json::Value = serde_json::to_value(&cmd).unwrap();
assert!(v.get("cmd").is_some());
assert!(v.get("params").is_some());
}
#[test]
fn zc_response_from_firmware_json() {
let raw = r#"{"ok":true,"data":{"pin":25,"value":1,"state":"HIGH"}}"#;
let resp: ZcResponse = serde_json::from_str(raw).unwrap();
assert!(resp.ok);
assert_eq!(resp.data["state"], "HIGH");
}
#[test]
fn zc_response_missing_optional_fields() {
let raw = r#"{"ok":true}"#;
let resp: ZcResponse = serde_json::from_str(raw).unwrap();
assert!(resp.ok);
assert!(resp.data.is_null());
assert!(resp.error.is_none());
}
}