worterbuch_common/
client.rs

1/*
2 *  Worterbuch client messages module
3 *
4 *  Copyright (C) 2024 Michael Bachmann
5 *
6 *  This program is free software: you can redistribute it and/or modify
7 *  it under the terms of the GNU Affero General Public License as published by
8 *  the Free Software Foundation, either version 3 of the License, or
9 *  (at your option) any later version.
10 *
11 *  This program is distributed in the hope that it will be useful,
12 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 *  GNU Affero General Public License for more details.
15 *
16 *  You should have received a copy of the GNU Affero General Public License
17 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
18 */
19
20use crate::{
21    AuthToken, Key, LiveOnlyFlag, ProtocolVersionSegment, RequestPattern, TransactionId,
22    UniqueFlag, Value,
23};
24use serde::{Deserialize, Serialize};
25
26#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "camelCase")]
28pub enum ClientMessage {
29    ProtocolSwitchRequest(ProtocolSwitchRequest),
30    AuthorizationRequest(AuthorizationRequest),
31    Get(Get),
32    CGet(Get),
33    PGet(PGet),
34    Set(Set),
35    CSet(CSet),
36    SPubInit(SPubInit),
37    SPub(SPub),
38    Publish(Publish),
39    Subscribe(Subscribe),
40    PSubscribe(PSubscribe),
41    Unsubscribe(Unsubscribe),
42    Delete(Delete),
43    PDelete(PDelete),
44    Ls(Ls),
45    PLs(PLs),
46    SubscribeLs(SubscribeLs),
47    UnsubscribeLs(UnsubscribeLs),
48    Lock(Lock),
49    ReleaseLock(Lock),
50    Transform(Transform),
51}
52
53impl ClientMessage {
54    pub fn transaction_id(&self) -> Option<TransactionId> {
55        match self {
56            ClientMessage::ProtocolSwitchRequest(_) | ClientMessage::AuthorizationRequest(_) => {
57                Some(0)
58            }
59            ClientMessage::Get(m) | ClientMessage::CGet(m) => Some(m.transaction_id),
60            ClientMessage::PGet(m) => Some(m.transaction_id),
61            ClientMessage::Set(m) => Some(m.transaction_id),
62            ClientMessage::CSet(m) => Some(m.transaction_id),
63            ClientMessage::SPubInit(m) => Some(m.transaction_id),
64            ClientMessage::SPub(m) => Some(m.transaction_id),
65            ClientMessage::Publish(m) => Some(m.transaction_id),
66            ClientMessage::Subscribe(m) => Some(m.transaction_id),
67            ClientMessage::PSubscribe(m) => Some(m.transaction_id),
68            ClientMessage::Unsubscribe(m) => Some(m.transaction_id),
69            ClientMessage::Delete(m) => Some(m.transaction_id),
70            ClientMessage::PDelete(m) => Some(m.transaction_id),
71            ClientMessage::Ls(m) => Some(m.transaction_id),
72            ClientMessage::PLs(m) => Some(m.transaction_id),
73            ClientMessage::SubscribeLs(m) => Some(m.transaction_id),
74            ClientMessage::UnsubscribeLs(m) => Some(m.transaction_id),
75            ClientMessage::Lock(m) => Some(m.transaction_id),
76            ClientMessage::ReleaseLock(m) => Some(m.transaction_id),
77            ClientMessage::Transform(m) => Some(m.transaction_id),
78        }
79    }
80}
81#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
82#[serde(rename_all = "camelCase")]
83pub struct ProtocolSwitchRequest {
84    pub version: ProtocolVersionSegment,
85}
86
87#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
88#[serde(rename_all = "camelCase")]
89pub struct AuthorizationRequest {
90    pub auth_token: AuthToken,
91}
92
93#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
94#[serde(rename_all = "camelCase")]
95pub struct Get {
96    pub transaction_id: TransactionId,
97    pub key: Key,
98}
99
100#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
101#[serde(rename_all = "camelCase")]
102pub struct PGet {
103    pub transaction_id: TransactionId,
104    pub request_pattern: RequestPattern,
105}
106
107#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
108#[serde(rename_all = "camelCase")]
109pub struct Set {
110    pub transaction_id: TransactionId,
111    pub key: Key,
112    pub value: Value,
113}
114
115#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
116#[serde(rename_all = "camelCase")]
117pub struct CSet {
118    pub transaction_id: TransactionId,
119    pub key: Key,
120    pub value: Value,
121    pub version: u64,
122}
123
124#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
125#[serde(rename_all = "camelCase")]
126pub struct SPubInit {
127    pub transaction_id: TransactionId,
128    pub key: Key,
129}
130
131#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
132#[serde(rename_all = "camelCase")]
133pub struct SPub {
134    pub transaction_id: TransactionId,
135    pub value: Value,
136}
137
138#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
139#[serde(rename_all = "camelCase")]
140pub struct Publish {
141    pub transaction_id: TransactionId,
142    pub key: Key,
143    pub value: Value,
144}
145#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
146#[serde(rename_all = "camelCase")]
147pub struct Subscribe {
148    pub transaction_id: TransactionId,
149    pub key: RequestPattern,
150    pub unique: UniqueFlag,
151    #[serde(skip_serializing_if = "Option::is_none")]
152    pub live_only: Option<LiveOnlyFlag>,
153}
154
155#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
156#[serde(rename_all = "camelCase")]
157pub struct PSubscribe {
158    pub transaction_id: TransactionId,
159    pub request_pattern: RequestPattern,
160    pub unique: UniqueFlag,
161    #[serde(skip_serializing_if = "Option::is_none")]
162    pub aggregate_events: Option<u64>,
163    #[serde(skip_serializing_if = "Option::is_none")]
164    pub live_only: Option<LiveOnlyFlag>,
165}
166
167#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
168#[serde(rename_all = "camelCase")]
169pub struct Unsubscribe {
170    pub transaction_id: TransactionId,
171}
172
173#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
174#[serde(rename_all = "camelCase")]
175pub struct Delete {
176    pub transaction_id: TransactionId,
177    pub key: Key,
178}
179
180#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
181#[serde(rename_all = "camelCase")]
182pub struct PDelete {
183    pub transaction_id: TransactionId,
184    pub request_pattern: RequestPattern,
185    pub quiet: Option<bool>,
186}
187
188#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
189#[serde(rename_all = "camelCase")]
190pub struct Ls {
191    pub transaction_id: TransactionId,
192    pub parent: Option<Key>,
193}
194
195#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
196#[serde(rename_all = "camelCase")]
197pub struct PLs {
198    pub transaction_id: TransactionId,
199    pub parent_pattern: Option<RequestPattern>,
200}
201
202#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
203#[serde(rename_all = "camelCase")]
204pub struct SubscribeLs {
205    pub transaction_id: TransactionId,
206    pub parent: Option<Key>,
207}
208
209#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
210#[serde(rename_all = "camelCase")]
211pub struct UnsubscribeLs {
212    pub transaction_id: TransactionId,
213}
214
215#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
216#[serde(rename_all = "camelCase")]
217pub struct Lock {
218    pub transaction_id: TransactionId,
219    pub key: Key,
220}
221
222#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
223#[serde(rename_all = "camelCase")]
224pub struct Transform {
225    pub transaction_id: TransactionId,
226    pub key: Key,
227    pub template: Value,
228}
229
230#[cfg(test)]
231mod test {
232
233    use super::*;
234    use serde_json::json;
235
236    #[test]
237    fn auth_request_is_serialized_correctly() {
238        let msg = ClientMessage::AuthorizationRequest(AuthorizationRequest {
239            auth_token: "123456".to_owned(),
240        });
241
242        let json = r#"{"authorizationRequest":{"authToken":"123456"}}"#;
243
244        assert_eq!(&serde_json::to_string(&msg).unwrap(), json);
245    }
246
247    #[test]
248    fn auth_request_is_deserialized_correctly() {
249        let msg = ClientMessage::AuthorizationRequest(AuthorizationRequest {
250            auth_token: "123456".to_owned(),
251        });
252
253        let json = r#"{
254            "authorizationRequest": {
255              "authToken": "123456"
256            }
257          }"#;
258
259        assert_eq!(serde_json::from_str::<ClientMessage>(json).unwrap(), msg);
260    }
261
262    #[test]
263    fn set_is_deserialized_correctly() {
264        let json = r#"{"set": {"transactionId": 2, "key": "hello/world", "value": { "this value": "is a ", "complex": "JSON object"}}}"#;
265        let msg = serde_json::from_str::<ClientMessage>(json).unwrap();
266        assert_eq!(
267            msg,
268            ClientMessage::Set(Set {
269                transaction_id: 2,
270                key: "hello/world".to_owned(),
271                value: json!({ "this value": "is a ", "complex": "JSON object"}),
272            })
273        );
274    }
275
276    #[test]
277    fn psubscribe_without_aggregation_is_serialized_correctly() {
278        let msg = ClientMessage::PSubscribe(PSubscribe {
279            transaction_id: 1,
280            request_pattern: "hello/world".to_owned(),
281            unique: true,
282            aggregate_events: None,
283            live_only: None,
284        });
285
286        let json = serde_json::to_string(&msg).unwrap();
287        assert_eq!(
288            json,
289            r#"{"pSubscribe":{"transactionId":1,"requestPattern":"hello/world","unique":true}}"#
290        );
291    }
292
293    #[test]
294    fn psubscribe_with_aggregation_is_serialized_correctly() {
295        let msg = ClientMessage::PSubscribe(PSubscribe {
296            transaction_id: 1,
297            request_pattern: "hello/world".to_owned(),
298            unique: true,
299            aggregate_events: Some(10),
300            live_only: Some(true),
301        });
302
303        let json = serde_json::to_string(&msg).unwrap();
304        assert_eq!(
305            json,
306            r#"{"pSubscribe":{"transactionId":1,"requestPattern":"hello/world","unique":true,"aggregateEvents":10,"liveOnly":true}}"#
307        );
308    }
309
310    #[test]
311    fn psubscribe_without_aggregation_is_deserialized_correctly() {
312        let json =
313            r#"{"pSubscribe":{"transactionId":1,"requestPattern":"hello/world","unique":true}}"#;
314        let msg: ClientMessage = serde_json::from_str(json).unwrap();
315
316        assert_eq!(
317            msg,
318            ClientMessage::PSubscribe(PSubscribe {
319                transaction_id: 1,
320                request_pattern: "hello/world".to_owned(),
321                unique: true,
322                aggregate_events: None,
323                live_only: None,
324            })
325        );
326    }
327
328    #[test]
329    fn psubscribe_with_aggregation_is_deserialized_correctly() {
330        let json = r#"{"pSubscribe":{"transactionId":1,"requestPattern":"hello/world","unique":true,"aggregateEvents":10,"liveOnly":false}}"#;
331        let msg: ClientMessage = serde_json::from_str(json).unwrap();
332
333        assert_eq!(
334            msg,
335            ClientMessage::PSubscribe(PSubscribe {
336                transaction_id: 1,
337                request_pattern: "hello/world".to_owned(),
338                unique: true,
339                aggregate_events: Some(10),
340                live_only: Some(false),
341            })
342        );
343    }
344
345    #[test]
346    fn transform_is_serialized_correctly() {
347        let msg = ClientMessage::Transform(Transform {
348            transaction_id: 123,
349            key: "test/transformed/key".to_owned(),
350            template: json!({
351              "name": "@some/person/name",
352              "email": "@some/person/email",
353              "phone": "@some/person/phone",
354              "meta": {
355                "nested": "@some/completely/unrelated/key",
356                "info": "this is not a key reference and will remain in the transformed state"
357              }
358            }),
359        });
360
361        let json = serde_json::to_string(&msg).unwrap();
362        assert_eq!(
363            json,
364            r#"{"transform":{"transactionId":123,"key":"test/transformed/key","template":{"email":"@some/person/email","meta":{"info":"this is not a key reference and will remain in the transformed state","nested":"@some/completely/unrelated/key"},"name":"@some/person/name","phone":"@some/person/phone"}}}"#
365        );
366    }
367
368    #[test]
369    fn transform_is_deserialized_correctly() {
370        let json = r#"{
371                "transform": {
372                  "transactionId": 123,
373                  "key": "test/transformed/key",
374                  "template": {
375                    "name": "@some/person/name",
376                    "email": "@some/person/email",
377                    "phone": "@some/person/phone",
378                    "meta": {
379                      "nested": "@some/completely/unrelated/key",
380                      "info": "this is not a key reference and will remain in the transformed state"
381                    }
382                  }
383                }
384              }
385              "#;
386        let msg: ClientMessage = serde_json::from_str(json).unwrap();
387
388        assert_eq!(
389            msg,
390            ClientMessage::Transform(Transform {
391                transaction_id: 123,
392                key: "test/transformed/key".to_owned(),
393                template: json!({
394                  "name": "@some/person/name",
395                  "email": "@some/person/email",
396                  "phone": "@some/person/phone",
397                  "meta": {
398                    "nested": "@some/completely/unrelated/key",
399                    "info": "this is not a key reference and will remain in the transformed state"
400                  }
401                }),
402            })
403        );
404    }
405
406    #[test]
407    fn spub_init_is_deserialized_correctly() {
408        let json = r#"{"sPubInit": {"transactionId": 2, "key": "hello/world"}}"#;
409        let expected = ClientMessage::SPubInit(SPubInit {
410            transaction_id: 2,
411            key: "hello/world".into(),
412        });
413        assert_eq!(
414            serde_json::from_str::<ClientMessage>(json).unwrap(),
415            expected
416        );
417    }
418
419    #[test]
420    fn spub_is_deserialized_correctly() {
421        let json = r#"{"sPub": {"transactionId": 2, "value": 123}}"#;
422        let expected = ClientMessage::SPub(SPub {
423            transaction_id: 2,
424            value: json!(123),
425        });
426        assert_eq!(
427            serde_json::from_str::<ClientMessage>(json).unwrap(),
428            expected
429        );
430    }
431
432    #[test]
433    fn spub_init_is_serialized_correctly() {
434        let json = r#"{"sPubInit":{"transactionId":2,"key":"hello/world"}}"#;
435        let expected = SPubInit {
436            transaction_id: 2,
437            key: "hello/world".into(),
438        };
439        assert_eq!(
440            &serde_json::to_string(&ClientMessage::SPubInit(expected)).unwrap(),
441            json
442        );
443    }
444
445    #[test]
446    fn spub_is_serialized_correctly() {
447        let json = r#"{"sPub":{"transactionId":2,"value":123}}"#;
448        let expected = SPub {
449            transaction_id: 2,
450            value: json!(123),
451        };
452        assert_eq!(
453            &serde_json::to_string(&ClientMessage::SPub(expected)).unwrap(),
454            json
455        );
456    }
457}