orchestra_toolkit/
locutor.rs

1/* Copyright 2024-2025 LEDR Technologies Inc.
2* This file is part of the Orchestra library, which helps developer use our Orchestra technology which is based on AvesTerra, owned and developped by Georgetown University, under license agreement with LEDR Technologies Inc.
3*
4* The Orchestra library is a free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or any later version.
5*
6* The Orchestra library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
7*
8* You should have received a copy of the GNU Lesser General Public License along with the Orchestra library. If not, see <https://www.gnu.org/licenses/>.
9*
10* If you have any questions, feedback or issues about the Orchestra library, you can contact us at support@ledr.io.
11*/
12
13use serde::{Deserialize, Serialize};
14use time::OffsetDateTime;
15
16use crate::*;
17
18#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
19#[serde(try_from = "LocutorSerial", into = "LocutorSerial")]
20pub struct Locutor {
21    pub entity: Entity,
22    pub outlet: Entity,
23    pub auxiliary: Entity,
24    pub ancillary: Entity,
25    pub context: Context,
26    pub category: Category,
27    pub class: Class,
28    pub method: Method,
29    pub attribute: Attribute,
30    pub instance: i32,
31    pub offset: i32,
32    pub parameter: i64,
33    pub resultant: i64,
34    pub count: i64,
35    pub index: i64,
36    pub event: Event,
37    pub mode: Mode,
38    pub state: State,
39    pub condition: Condition,
40    pub precedence: u16,
41    pub time: OffsetDateTime,
42    pub timeout: i64,
43    pub aspect: Aspect,
44    pub template: Template,
45    pub scheme: Scheme,
46    pub name: String255,
47    pub label: String,
48    pub key: String255,
49    /// Both the 'value' and 'value_tag' fields are serialized from this
50    pub value: Value,
51    pub format: Format,
52    pub authority: Token,
53    pub authorization: Token,
54}
55
56impl Default for Locutor {
57    fn default() -> Self {
58        Locutor {
59            entity: Entity::NULL,
60            outlet: Entity::NULL,
61            auxiliary: Entity::NULL,
62            ancillary: Entity::NULL,
63            context: Context::Null,
64            category: Category::Null,
65            class: Class::Null,
66            method: Method::Null,
67            attribute: Attribute::Null,
68            instance: 0,
69            offset: 0,
70            parameter: 0,
71            resultant: 0,
72            count: 0,
73            index: 0,
74            event: Event::Null,
75            mode: Mode::Null,
76            state: State::Null,
77            condition: Condition::Null,
78            precedence: 0,
79            time: OffsetDateTime::UNIX_EPOCH,
80            timeout: 0,
81            aspect: Aspect::Null,
82            template: Template::Null,
83            scheme: Scheme::Null,
84            name: String255::NULL,
85            label: String::new(),
86            key: String255::NULL,
87            value: Value::NULL,
88            format: Format::Null,
89            authority: Token::NULL,
90            authorization: Token::NULL,
91        }
92    }
93}
94
95impl Locutor {
96    #[rustfmt::skip]
97    /// Merge two Locutor structs, taking the first non-null value from each field
98    pub fn merge(&self, other: &Self) -> Self {
99        Locutor {
100            entity: if self.entity.is_null() { other.entity } else { self.entity },
101            outlet: if self.outlet.is_null() { other.outlet } else { self.outlet },
102            auxiliary: if self.auxiliary.is_null() { other.auxiliary } else { self.auxiliary },
103            ancillary: if self.ancillary.is_null() { other.ancillary } else { self.ancillary },
104            context: if self.context.is_null() { other.context } else { self.context },
105            category: if self.category.is_null() { other.category } else { self.category },
106            class: if self.class.is_null() { other.class } else { self.class },
107            method: if self.method.is_null() { other.method } else { self.method },
108            attribute: if self.attribute.is_null() { other.attribute } else { self.attribute },
109            instance: if self.instance == 0 { other.instance } else { self.instance },
110            offset: if self.offset == 0 { other.offset } else { self.offset },
111            parameter: if self.parameter == 0 { other.parameter } else { self.parameter },
112            resultant: if self.resultant == 0 { other.resultant } else { self.resultant },
113            count: if self.count == 0 { other.count } else { self.count },
114            index: if self.index == 0 { other.index } else { self.index },
115            event: if self.event.is_null() { other.event } else { self.event },
116            mode: if self.mode.is_null() { other.mode } else { self.mode },
117            state: if self.state.is_null() { other.state } else { self.state },
118            condition: if self.condition.is_null() { other.condition } else { self.condition },
119            precedence: if self.precedence == 0 { other.precedence } else { self.precedence },
120            time: if self.time == OffsetDateTime::UNIX_EPOCH { other.time } else { self.time },
121            timeout: if self.timeout == 0 { other.timeout } else { self.timeout },
122            aspect: if self.aspect.is_null() { other.aspect } else { self.aspect },
123            template: if self.template.is_null() { other.template } else { self.template },
124            scheme: if self.scheme.is_null() { other.scheme } else { self.scheme },
125            name: if self.name.is_null() { other.name.clone() } else { self.name.clone() },
126            label: if self.label.is_empty() { other.label.clone() } else { self.label.clone() },
127            key: if self.key.is_null() { other.key.clone() } else { self.key.clone() },
128            value: if self.value.is_null() { other.value.clone() } else { self.value.clone() },
129            format: if self.format.is_null() { other.format } else { self.format },
130            authority: if self.authority.is_null() { other.authority } else { self.authority },
131            authorization: if self.authorization.is_null() { other.authorization } else { self.authorization },
132        }
133    }
134}
135
136// ----------------------------------------------------------------------------
137
138/// This utility struct is used to serialize and deserialize the Locutor struct.
139/// The main differecence is that the 'value' and 'value_tag' are separate
140#[derive(Debug, Serialize, Deserialize, PartialEq)]
141#[serde(default, rename_all = "UPPERCASE")]
142struct LocutorSerial {
143    #[serde(skip_serializing_if = "Entity::is_null")]
144    entity: Entity,
145    #[serde(skip_serializing_if = "Entity::is_null")]
146    outlet: Entity,
147    #[serde(skip_serializing_if = "Entity::is_null")]
148    auxiliary: Entity,
149    #[serde(skip_serializing_if = "Entity::is_null")]
150    ancillary: Entity,
151    #[serde(skip_serializing_if = "Context::is_null")]
152    context: Context,
153    #[serde(skip_serializing_if = "Category::is_null")]
154    category: Category,
155    #[serde(skip_serializing_if = "Class::is_null")]
156    class: Class,
157    #[serde(skip_serializing_if = "Method::is_null")]
158    method: Method,
159    #[serde(skip_serializing_if = "Attribute::is_null")]
160    attribute: Attribute,
161    #[serde(skip_serializing_if = "i32_is_zero")]
162    instance: i32,
163    #[serde(skip_serializing_if = "i32_is_zero")]
164    offset: i32,
165    #[serde(skip_serializing_if = "i64_is_zero")]
166    parameter: i64,
167    #[serde(skip_serializing_if = "i64_is_zero")]
168    resultant: i64,
169    #[serde(skip_serializing_if = "i64_is_zero")]
170    count: i64,
171    #[serde(skip_serializing_if = "i64_is_zero")]
172    index: i64,
173    #[serde(skip_serializing_if = "Event::is_null")]
174    event: Event,
175    #[serde(skip_serializing_if = "Mode::is_null")]
176    mode: Mode,
177    #[serde(skip_serializing_if = "State::is_null")]
178    state: State,
179    #[serde(skip_serializing_if = "Condition::is_null")]
180    condition: Condition,
181    #[serde(skip_serializing_if = "u16_is_zero", with = "locutor_precedence_serde")]
182    precedence: u16,
183    #[serde(skip_serializing_if = "time_serde::is_zero", with = "time_serde")]
184    time: OffsetDateTime,
185    #[serde(skip_serializing_if = "i64_is_zero")]
186    timeout: i64,
187    #[serde(skip_serializing_if = "Aspect::is_null")]
188    aspect: Aspect,
189    #[serde(skip_serializing_if = "Template::is_null")]
190    template: Template,
191    #[serde(skip_serializing_if = "Scheme::is_null")]
192    scheme: Scheme,
193    #[serde(skip_serializing_if = "String::is_empty")]
194    name: String255,
195    #[serde(skip_serializing_if = "String::is_empty")]
196    label: String,
197    #[serde(skip_serializing_if = "String::is_empty")]
198    key: String255,
199    #[serde(skip_serializing_if = "String::is_empty")]
200    value: String,
201    #[serde(skip_serializing_if = "Tag::is_null")]
202    value_tag: Tag,
203    #[serde(skip_serializing_if = "Format::is_null")]
204    format: Format,
205    #[serde(skip_serializing_if = "Token::is_null")]
206    authority: Token,
207    #[serde(skip_serializing_if = "Token::is_null")]
208    authorization: Token,
209}
210
211fn i32_is_zero(n: &i32) -> bool {
212    *n == 0
213}
214
215fn i64_is_zero(n: &i64) -> bool {
216    *n == 0
217}
218
219fn u16_is_zero(n: &u16) -> bool {
220    *n == 0
221}
222
223impl From<Locutor> for LocutorSerial {
224    fn from(locutor: Locutor) -> Self {
225        LocutorSerial {
226            entity: locutor.entity,
227            outlet: locutor.outlet,
228            auxiliary: locutor.auxiliary,
229            ancillary: locutor.ancillary,
230            context: locutor.context,
231            category: locutor.category,
232            class: locutor.class,
233            method: locutor.method,
234            attribute: locutor.attribute,
235            instance: locutor.instance,
236            offset: locutor.offset,
237            parameter: locutor.parameter,
238            resultant: locutor.resultant,
239            count: locutor.count,
240            index: locutor.index,
241            event: locutor.event,
242            mode: locutor.mode,
243            state: locutor.state,
244            condition: locutor.condition,
245            precedence: locutor.precedence,
246            time: locutor.time,
247            timeout: locutor.timeout,
248            aspect: locutor.aspect,
249            template: locutor.template,
250            scheme: locutor.scheme,
251            name: locutor.name,
252            label: locutor.label,
253            key: locutor.key,
254            value: locutor.value.get_bytes(),
255            value_tag: locutor.value.get_tag(),
256            format: locutor.format,
257            authority: locutor.authority,
258            authorization: locutor.authorization,
259        }
260    }
261}
262
263impl TryFrom<LocutorSerial> for Locutor {
264    type Error = ValueCreationError;
265
266    fn try_from(locutor: LocutorSerial) -> Result<Self, Self::Error> {
267        Ok(Locutor {
268            entity: locutor.entity,
269            outlet: locutor.outlet,
270            auxiliary: locutor.auxiliary,
271            ancillary: locutor.ancillary,
272            context: locutor.context,
273            category: locutor.category,
274            class: locutor.class,
275            method: locutor.method,
276            attribute: locutor.attribute,
277            instance: locutor.instance,
278            offset: locutor.offset,
279            parameter: locutor.parameter,
280            resultant: locutor.resultant,
281            count: locutor.count,
282            index: locutor.index,
283            event: locutor.event,
284            mode: locutor.mode,
285            state: locutor.state,
286            condition: locutor.condition,
287            precedence: locutor.precedence,
288            time: locutor.time,
289            timeout: locutor.timeout,
290            aspect: locutor.aspect,
291            template: locutor.template,
292            scheme: locutor.scheme,
293            name: locutor.name,
294            label: locutor.label,
295            key: locutor.key,
296            value: Value::new(locutor.value_tag, locutor.value)?,
297            format: locutor.format,
298            authority: locutor.authority,
299            authorization: locutor.authorization,
300        })
301    }
302}
303
304impl Default for LocutorSerial {
305    fn default() -> Self {
306        Locutor::default().into()
307    }
308}
309
310/// We deserialize either number or string representing number
311/// We serialize the number as a string, like the standard Avial adapter
312mod locutor_precedence_serde {
313    use serde::de;
314    use serde::ser;
315
316    pub fn serialize<S>(precedence: &u16, serializer: S) -> Result<S::Ok, S::Error>
317    where
318        S: ser::Serializer,
319    {
320        serializer.serialize_str(&precedence.to_string())
321    }
322
323    pub fn deserialize<'de, D>(deserializer: D) -> Result<u16, D::Error>
324    where
325        D: de::Deserializer<'de>,
326    {
327        match serde::Deserialize::deserialize(deserializer)? {
328            serde_json::Value::Number(n) => n
329                .as_u64()
330                .ok_or_else(|| de::Error::custom("Invalid number"))
331                .map(|n| n as u16),
332            serde_json::Value::String(s) => s
333                .trim()
334                .parse()
335                .map_err(|_| de::Error::custom("Invalid string"))
336                .map(|n: u16| n),
337            _ => Err(de::Error::custom("Invalid value")),
338        }
339    }
340}
341
342#[cfg(test)]
343mod test {
344    use std::str::FromStr;
345
346    use super::*;
347    use ascii::AsciiString;
348    use serde_json::json;
349
350    #[test]
351    fn example_json() {
352        let json = json! (
353            {
354                "ENTITY": "<0|0|24>",
355                "OUTLET": "<0|0|11>",
356                "AUXILIARY": "<0|0|1>",
357                "ANCILLARY": "<0|0|2>",
358                "CONTEXT": "AVESTERRA_CONTEXT",
359                "CATEGORY": "AVESTERRA_CATEGORY",
360                "CLASS": "AVESTERRA_CLASS",
361                "METHOD": "AVESTERRA_METHOD",
362                "ATTRIBUTE": "AVESTERRA_ATTRIBUTE",
363                "INSTANCE": 1,
364                "NAME": "Example Name",
365                "VALUE": "Example String",
366                "VALUE_TAG": "STRING_TAG",
367                "INDEX": 1,
368                "COUNT": 123,
369                "PRECEDENCE": " 8",
370                "PARAMETER": -1,
371                "MODE": "AVESTERRA_MODE",
372                "EVENT": "AVESTERRA_EVENT",
373                "TIMEOUT": 60,
374                "ASPECT": "AVESTERRA_ASPECT",
375                "AUTHORITY": "********-****-****-****-************"
376            }
377        );
378
379        assert_eq!(
380            serde_json::from_value::<Locutor>(json).unwrap(),
381            Locutor {
382                entity: Entity::new(0, 0, 24),
383                outlet: Entity::new(0, 0, 11),
384                auxiliary: Entity::new(0, 0, 1),
385                ancillary: Entity::new(0, 0, 2),
386                context: Context::Avesterra,
387                category: Category::Avesterra,
388                class: Class::Avesterra,
389                method: Method::Avesterra,
390                attribute: Attribute::Avesterra,
391                instance: 1,
392                offset: 0,
393                parameter: -1,
394                resultant: 0,
395                count: 123,
396                index: 1,
397                event: Event::Avesterra,
398                mode: Mode::Avesterra,
399                state: State::Null,
400                condition: Condition::Null,
401                precedence: 8,
402                time: OffsetDateTime::from_unix_timestamp(0).unwrap(),
403                timeout: 60,
404                aspect: Aspect::Avesterra,
405                template: Template::Null,
406                scheme: Scheme::Null,
407                name: String255::unchecked("Example Name"),
408                label: String::new(),
409                key: String255::NULL,
410                value: Value::String(AsciiString::from_str("Example String").unwrap()),
411                format: Format::Null,
412                authority: Token::from_str("********-****-****-****-************").unwrap(),
413                authorization: Token::NULL,
414            }
415        )
416    }
417
418    #[test]
419    fn empty() {
420        let json = json!({});
421
422        assert_eq!(
423            serde_json::from_value::<Locutor>(json).unwrap(),
424            Locutor {
425                ..Default::default()
426            }
427        )
428    }
429
430    #[test]
431    fn precedence_as_string() {
432        let json = json! (
433            {
434                "PRECEDENCE": " 983  ",
435            }
436        );
437
438        assert_eq!(
439            serde_json::from_value::<Locutor>(json).unwrap(),
440            Locutor {
441                precedence: 983,
442                ..Default::default()
443            }
444        )
445    }
446
447    #[test]
448    fn precedence_as_number() {
449        let json = json! (
450            {
451                "PRECEDENCE": 123,
452            }
453        );
454
455        assert_eq!(
456            serde_json::from_value::<Locutor>(json).unwrap(),
457            Locutor {
458                precedence: 123,
459                ..Default::default()
460            }
461        )
462    }
463
464    #[test]
465    fn value_null() {
466        let json = json!({
467            "VALUE": "2398",
468            "VALUE_TAG": "INTEGER_TAG",
469        });
470
471        assert_eq!(
472            serde_json::from_value::<Locutor>(json).unwrap(),
473            Locutor {
474                value: Value::Integer(2398),
475                ..Default::default()
476            }
477        )
478    }
479
480    #[test]
481    fn serialize_empty() {
482        let v: serde_json::Value = serde_json::to_value(Locutor::default()).unwrap();
483
484        assert_eq!(v, json!({}))
485    }
486
487    #[test]
488    fn serialize_one_field() {
489        let loc = Locutor {
490            entity: Entity::new(0, 0, 24),
491            ..Default::default()
492        };
493        let v: serde_json::Value = serde_json::to_value(loc).unwrap();
494
495        assert_eq!(v, json!({"ENTITY": "<0|0|24>"}))
496    }
497
498    #[test]
499    fn negative_values() {
500        let json = json!({
501            "TIMEOUT": -1,
502            "INDEX": -2,
503            "COUNT": -3,
504            "RESULTANT": -4,
505            "PARAMETER": -5,
506            "OFFSET": -6,
507            "INSTANCE": -7,
508        });
509
510        let loc = serde_json::from_value::<Locutor>(json).unwrap();
511        assert_eq!(
512            loc,
513            Locutor {
514                timeout: -1,
515                index: -2,
516                count: -3,
517                resultant: -4,
518                parameter: -5,
519                offset: -6,
520                instance: -7,
521                ..Default::default()
522            }
523        );
524
525        assert_eq!(
526            serde_json::to_value(loc).unwrap(),
527            json!({
528                "TIMEOUT": -1,
529                "INDEX": -2,
530                "COUNT": -3,
531                "RESULTANT": -4,
532                "PARAMETER": -5,
533                "OFFSET": -6,
534                "INSTANCE": -7,
535            })
536        );
537    }
538}