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