use serde::{Deserialize, Serialize};
use serde_json::Value;
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeviceRegisterBody {
pub consumer_kind: Value,
pub display_name: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub platform: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub hpke_public_key: Option<String>,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeviceHeartbeatBody {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub platform: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub vault_seq: Option<u64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeviceDisableBody {
pub device_id: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub reason: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeviceWipeBody {
pub device_id: String,
pub scope: String,
pub reason: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct WakeHandle {
pub gateway: String,
pub handle: String,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct DeviceSetWakeBody {
#[serde(default, skip_serializing_if = "Option::is_none")]
pub wake_handle: Option<WakeHandle>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub suggested_triggers: Option<Vec<String>>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn a_bare_heartbeat_is_an_empty_object() {
assert_eq!(
serde_json::to_value(DeviceHeartbeatBody::default()).expect("serialises"),
serde_json::json!({})
);
}
#[test]
fn an_unset_register_member_is_absent() {
let minimal = DeviceRegisterBody {
consumer_kind: serde_json::json!({"kind": "companion", "formFactor": "desktop"}),
display_name: "laptop".into(),
platform: None,
hpke_public_key: None,
};
assert_eq!(
serde_json::to_value(&minimal).expect("serialises"),
serde_json::json!({
"consumerKind": {"kind": "companion", "formFactor": "desktop"},
"displayName": "laptop",
})
);
}
#[test]
fn set_members_serialise_camel_case() {
let full = DeviceRegisterBody {
consumer_kind: serde_json::json!({"kind": "service", "serviceKind": "ai-agent"}),
display_name: "agent".into(),
platform: Some("macos".into()),
hpke_public_key: Some("zHpke".into()),
};
let v = serde_json::to_value(&full).expect("serialises");
assert_eq!(v.get("platform").and_then(Value::as_str), Some("macos"));
assert_eq!(
v.get("hpkePublicKey").and_then(Value::as_str),
Some("zHpke")
);
}
#[test]
fn a_wake_handle_nests_under_its_camel_case_member() {
let body = DeviceSetWakeBody {
wake_handle: Some(WakeHandle {
gateway: "apns".into(),
handle: "opaque".into(),
}),
suggested_triggers: Some(vec!["message".into()]),
};
assert_eq!(
serde_json::to_value(&body).expect("serialises"),
serde_json::json!({
"wakeHandle": {"gateway": "apns", "handle": "opaque"},
"suggestedTriggers": ["message"],
})
);
}
}