use serde::{Deserialize, Serialize};
use uuid::Uuid;
pub const PAIRING_STATUS_NOTIFY_METHOD: &str = "nexo/notify/pairing_status_changed";
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PairingStartInput {
pub agent_id: String,
pub channel: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub instance: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PairingStartResponse {
pub challenge_id: Uuid,
pub expires_at_ms: u64,
pub instructions: String,
}
#[non_exhaustive]
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "snake_case")]
pub enum PairingState {
Pending,
QrReady,
AwaitingUser,
Linked,
Expired,
Cancelled,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PairingStatusParams {
pub challenge_id: Uuid,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PairingStatus {
pub challenge_id: Uuid,
pub state: PairingState,
#[serde(default, skip_serializing_if = "PairingStatusData::is_empty")]
pub data: PairingStatusData,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct PairingStatusData {
#[serde(skip_serializing_if = "Option::is_none")]
pub qr_ascii: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub qr_png_base64: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub device_jid: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub error: Option<String>,
}
impl PairingStatusData {
pub fn is_empty(&self) -> bool {
self.qr_ascii.is_none()
&& self.qr_png_base64.is_none()
&& self.device_jid.is_none()
&& self.error.is_none()
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct PairingCancelParams {
pub challenge_id: Uuid,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct PairingCancelResponse {
pub cancelled: bool,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn pairing_status_round_trip_qr_ready() {
let s = PairingStatus {
challenge_id: Uuid::nil(),
state: PairingState::QrReady,
data: PairingStatusData {
qr_ascii: Some("##".into()),
qr_png_base64: Some("AAAA".into()),
device_jid: None,
error: None,
},
};
let v = serde_json::to_value(&s).unwrap();
let back: PairingStatus = serde_json::from_value(v).unwrap();
assert_eq!(s, back);
}
#[test]
fn pairing_status_skips_empty_data() {
let s = PairingStatus {
challenge_id: Uuid::nil(),
state: PairingState::Pending,
data: PairingStatusData::default(),
};
let v = serde_json::to_value(&s).unwrap();
let obj = v.as_object().unwrap();
assert!(!obj.contains_key("data"));
}
#[test]
fn pairing_state_serialises_snake_case() {
let v = serde_json::to_value(PairingState::AwaitingUser).unwrap();
assert_eq!(v, serde_json::json!("awaiting_user"));
let v = serde_json::to_value(PairingState::QrReady).unwrap();
assert_eq!(v, serde_json::json!("qr_ready"));
}
#[test]
fn pairing_status_notify_method_constant() {
assert_eq!(
PAIRING_STATUS_NOTIFY_METHOD,
"nexo/notify/pairing_status_changed"
);
}
}