vertigo/websocket_collection/
ws_message_from.rs1use crate::{AutoJsJson, JsJson};
16
17use super::types::WebsocketQueryId;
18
19#[derive(Debug, Clone, AutoJsJson, PartialEq)]
20pub struct WsInitDataModel {
21 pub id: String,
22 pub model: JsJson,
23}
24
25#[derive(Debug, Clone, AutoJsJson, PartialEq)]
26pub struct WsInitData {
27 pub query_id: WebsocketQueryId,
28 pub list: Vec<WsInitDataModel>,
29}
30
31#[derive(Debug, Clone, AutoJsJson, PartialEq)]
32pub struct WsSetData {
33 pub query_id: WebsocketQueryId,
34 pub model_id: String,
35 pub model: JsJson,
36}
37
38#[derive(Debug, Clone, AutoJsJson, PartialEq)]
39pub struct WsDeleteData {
40 pub query_id: WebsocketQueryId,
41 pub model_id: String,
42}
43
44#[derive(Debug, Clone, AutoJsJson, PartialEq)]
45pub enum WsMessageKind {
46 Log,
47 Error,
48}
49
50#[derive(Debug, Clone, AutoJsJson, PartialEq)]
51#[js_json(rename_all = "camelCase")]
52pub struct WsMessageData {
53 pub query_id: WebsocketQueryId,
54 pub kind: WsMessageKind,
55 pub message: String,
56 pub code: Option<u16>,
57}
58
59#[derive(Debug, Clone, AutoJsJson, PartialEq)]
60pub enum WsServerMessageFrom {
61 #[js_json(rename = "init")]
62 Init(WsInitData),
63 #[js_json(rename = "set")]
64 Set(WsSetData),
65 #[js_json(rename = "delete")]
66 Delete(WsDeleteData),
67 #[js_json(rename = "batch")]
68 Batch(Vec<WsServerMessageFrom>),
69 #[js_json(rename = "message")]
70 Message(WsMessageData),
71}
72
73fn unknown_tag(json: &JsJson) -> Option<String> {
76 match json {
77 JsJson::Object(map) if map.len() == 1 => map.keys().next().cloned(),
78 _ => None,
79 }
80}
81
82impl WsServerMessageFrom {
83 pub(crate) fn from_js_json(json: JsJson) -> Result<Self, String> {
89 crate::from_json::<Self>(json.clone()).map_err(|err| match unknown_tag(&json) {
90 Some(tag) => format!("unknown variant `{tag}` (server/client protocol drift): {err}"),
91 None => err,
92 })
93 }
94
95 pub(crate) fn query_id(&self) -> Option<WebsocketQueryId> {
97 match self {
98 WsServerMessageFrom::Init(d) => Some(d.query_id.clone()),
99 WsServerMessageFrom::Set(d) => Some(d.query_id.clone()),
100 WsServerMessageFrom::Delete(d) => Some(d.query_id.clone()),
101 WsServerMessageFrom::Message(d) => Some(d.query_id.clone()),
102 WsServerMessageFrom::Batch(_) => None,
103 }
104 }
105}
106
107#[cfg(test)]
108mod tests {
109 use std::{collections::BTreeMap, error::Error};
110
111 use crate::{JsJson, JsJsonContext, JsJsonDeserialize, JsJsonNumber, to_json};
112
113 use super::{super::types::WebsocketQueryId, *};
114
115 fn ctx() -> JsJsonContext {
116 JsJsonContext::new("")
117 }
118
119 fn sample_model_js() -> JsJson {
120 JsJson::Object(BTreeMap::from([
121 ("name".into(), JsJson::String("Test".into())),
122 ("ref_id".into(), JsJson::String("1".into())),
123 ("id".into(), JsJson::Number(JsJsonNumber(1.0))),
124 ("flag".into(), JsJson::False),
125 ("owner_id".into(), JsJson::Number(JsJsonNumber(1.0))),
126 ]))
127 }
128
129 #[test]
130 fn ws_server_message_init_round_trips() -> Result<(), Box<dyn Error>> {
131 let msg = WsServerMessageFrom::Init(WsInitData {
132 query_id: WebsocketQueryId("q1".into()),
133 list: vec![WsInitDataModel {
134 id: "1".into(),
135 model: sample_model_js(),
136 }],
137 });
138 let json = to_json(msg.clone());
139 let back = crate::from_json::<WsServerMessageFrom>(json)?;
140 assert_eq!(back, msg);
141
142 Ok(())
143 }
144
145 #[test]
146 fn ws_server_message_set_round_trips() -> Result<(), Box<dyn Error>> {
147 let msg = WsServerMessageFrom::Set(WsSetData {
148 query_id: WebsocketQueryId("q1".into()),
149 model_id: "6".into(),
150 model: sample_model_js(),
151 });
152 let json = to_json(msg.clone());
153 let back = crate::from_json::<WsServerMessageFrom>(json)?;
154 assert_eq!(back, msg);
155
156 Ok(())
157 }
158
159 #[test]
160 fn ws_server_message_delete_round_trips() -> Result<(), Box<dyn Error>> {
161 let msg = WsServerMessageFrom::Delete(WsDeleteData {
162 query_id: WebsocketQueryId("query_0".into()),
163 model_id: "7".into(),
164 });
165 let json = to_json(msg.clone());
166 let back = crate::from_json::<WsServerMessageFrom>(json)?;
167 assert_eq!(back, msg);
168
169 Ok(())
170 }
171
172 #[test]
173 fn from_js_json_reports_unknown_variant_by_tag() -> Result<(), Box<dyn Error>> {
174 let raw = JsJson::Object(BTreeMap::from([(
177 "initOne".into(),
178 JsJson::Object(BTreeMap::from([
179 ("queryId".into(), JsJson::String("query_0".into())),
180 ("modelId".into(), JsJson::String("1".into())),
181 ("model".into(), JsJson::Number(JsJsonNumber(1.0))),
182 ])),
183 )]));
184
185 let err = WsServerMessageFrom::from_js_json(raw)
186 .err()
187 .ok_or_else(|| "expected error".to_string())?;
188 assert!(
189 err.contains("`initOne`"),
190 "error must name the unknown tag, got: {err}"
191 );
192
193 Ok(())
194 }
195
196 #[test]
197 fn ws_server_message_init_matches_from_json_on_jsjson() -> Result<(), Box<dyn Error>> {
198 let msg = WsServerMessageFrom::Init(WsInitData {
199 query_id: WebsocketQueryId("query_0".into()),
200 list: vec![WsInitDataModel {
201 id: "1".into(),
202 model: JsJson::Number(JsJsonNumber(1.0)),
203 }],
204 });
205 let json = to_json(msg.clone());
206 let from_public = crate::from_json::<WsServerMessageFrom>(json.clone())?;
207 let from_trait = WsServerMessageFrom::from_json(ctx(), json)?;
208 assert_eq!(from_public, msg);
209 assert_eq!(from_trait, msg);
210
211 Ok(())
212 }
213}