use crate::{AutoJsJson, JsJson};
use super::types::WebsocketQueryId;
#[derive(Debug, Clone, AutoJsJson, PartialEq)]
pub struct WsInitDataModel {
pub id: String,
pub model: JsJson,
}
#[derive(Debug, Clone, AutoJsJson, PartialEq)]
pub struct WsInitData {
pub query_id: WebsocketQueryId,
pub list: Vec<WsInitDataModel>,
}
#[derive(Debug, Clone, AutoJsJson, PartialEq)]
pub struct WsSetData {
pub query_id: WebsocketQueryId,
pub model_id: String,
pub model: JsJson,
}
#[derive(Debug, Clone, AutoJsJson, PartialEq)]
pub struct WsDeleteData {
pub query_id: WebsocketQueryId,
pub model_id: String,
}
#[derive(Debug, Clone, AutoJsJson, PartialEq)]
pub enum WsMessageKind {
Log,
Error,
}
#[derive(Debug, Clone, AutoJsJson, PartialEq)]
#[js_json(rename_all = "camelCase")]
pub struct WsMessageData {
pub query_id: WebsocketQueryId,
pub kind: WsMessageKind,
pub message: String,
pub code: Option<u16>,
}
#[derive(Debug, Clone, AutoJsJson, PartialEq)]
pub enum WsServerMessageFrom {
#[js_json(rename = "init")]
Init(WsInitData),
#[js_json(rename = "set")]
Set(WsSetData),
#[js_json(rename = "delete")]
Delete(WsDeleteData),
#[js_json(rename = "batch")]
Batch(Vec<WsServerMessageFrom>),
#[js_json(rename = "message")]
Message(WsMessageData),
}
fn unknown_tag(json: &JsJson) -> Option<String> {
match json {
JsJson::Object(map) if map.len() == 1 => map.keys().next().cloned(),
_ => None,
}
}
impl WsServerMessageFrom {
pub(crate) fn from_js_json(json: JsJson) -> Result<Self, String> {
crate::from_json::<Self>(json.clone()).map_err(|err| match unknown_tag(&json) {
Some(tag) => format!("unknown variant `{tag}` (server/client protocol drift): {err}"),
None => err,
})
}
pub(crate) fn query_id(&self) -> Option<WebsocketQueryId> {
match self {
WsServerMessageFrom::Init(d) => Some(d.query_id.clone()),
WsServerMessageFrom::Set(d) => Some(d.query_id.clone()),
WsServerMessageFrom::Delete(d) => Some(d.query_id.clone()),
WsServerMessageFrom::Message(d) => Some(d.query_id.clone()),
WsServerMessageFrom::Batch(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use std::{collections::BTreeMap, error::Error};
use crate::{JsJson, JsJsonContext, JsJsonDeserialize, JsJsonNumber, to_json};
use super::{super::types::WebsocketQueryId, *};
fn ctx() -> JsJsonContext {
JsJsonContext::new("")
}
fn sample_model_js() -> JsJson {
JsJson::Object(BTreeMap::from([
("name".into(), JsJson::String("Test".into())),
("ref_id".into(), JsJson::String("1".into())),
("id".into(), JsJson::Number(JsJsonNumber(1.0))),
("flag".into(), JsJson::False),
("owner_id".into(), JsJson::Number(JsJsonNumber(1.0))),
]))
}
#[test]
fn ws_server_message_init_round_trips() -> Result<(), Box<dyn Error>> {
let msg = WsServerMessageFrom::Init(WsInitData {
query_id: WebsocketQueryId("q1".into()),
list: vec![WsInitDataModel {
id: "1".into(),
model: sample_model_js(),
}],
});
let json = to_json(msg.clone());
let back = crate::from_json::<WsServerMessageFrom>(json)?;
assert_eq!(back, msg);
Ok(())
}
#[test]
fn ws_server_message_set_round_trips() -> Result<(), Box<dyn Error>> {
let msg = WsServerMessageFrom::Set(WsSetData {
query_id: WebsocketQueryId("q1".into()),
model_id: "6".into(),
model: sample_model_js(),
});
let json = to_json(msg.clone());
let back = crate::from_json::<WsServerMessageFrom>(json)?;
assert_eq!(back, msg);
Ok(())
}
#[test]
fn ws_server_message_delete_round_trips() -> Result<(), Box<dyn Error>> {
let msg = WsServerMessageFrom::Delete(WsDeleteData {
query_id: WebsocketQueryId("query_0".into()),
model_id: "7".into(),
});
let json = to_json(msg.clone());
let back = crate::from_json::<WsServerMessageFrom>(json)?;
assert_eq!(back, msg);
Ok(())
}
#[test]
fn from_js_json_reports_unknown_variant_by_tag() -> Result<(), Box<dyn Error>> {
let raw = JsJson::Object(BTreeMap::from([(
"initOne".into(),
JsJson::Object(BTreeMap::from([
("queryId".into(), JsJson::String("query_0".into())),
("modelId".into(), JsJson::String("1".into())),
("model".into(), JsJson::Number(JsJsonNumber(1.0))),
])),
)]));
let err = WsServerMessageFrom::from_js_json(raw)
.err()
.ok_or_else(|| "expected error".to_string())?;
assert!(
err.contains("`initOne`"),
"error must name the unknown tag, got: {err}"
);
Ok(())
}
#[test]
fn ws_server_message_init_matches_from_json_on_jsjson() -> Result<(), Box<dyn Error>> {
let msg = WsServerMessageFrom::Init(WsInitData {
query_id: WebsocketQueryId("query_0".into()),
list: vec![WsInitDataModel {
id: "1".into(),
model: JsJson::Number(JsJsonNumber(1.0)),
}],
});
let json = to_json(msg.clone());
let from_public = crate::from_json::<WsServerMessageFrom>(json.clone())?;
let from_trait = WsServerMessageFrom::from_json(ctx(), json)?;
assert_eq!(from_public, msg);
assert_eq!(from_trait, msg);
Ok(())
}
}