orchestra_toolkit/
avial_model.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::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
14use serde_tuple::{Deserialize_tuple, Serialize_tuple};
15use std::collections::HashMap;
16
17use crate::{taxonomy::*, String255, Value};
18
19#[derive(Debug, Default, Clone, PartialEq, Deserialize)]
20#[serde(default, rename_all = "PascalCase")]
21pub struct AvialModel {
22    pub name: String255,
23    pub key: String255,
24
25    /// Length of the data in bytes
26    pub data: u64,
27
28    pub properties: Vec<Property>,
29    pub facts: Vec<Fact>,
30}
31
32impl Serialize for AvialModel {
33    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
34    where
35        S: Serializer,
36    {
37        let mut x = serializer.serialize_struct("AvialModel", 4)?;
38        x.serialize_field("Model", "Avial")?;
39        if !self.name.is_empty() {
40            x.serialize_field("Name", &self.name)?;
41        }
42        if !self.key.is_empty() {
43            x.serialize_field("Key", &self.key)?;
44        }
45        if self.data > 0 {
46            x.serialize_field("Data", &self.data)?;
47        }
48        if !self.properties.is_empty() {
49            x.serialize_field("Properties", &self.properties)?;
50        }
51        if !self.facts.is_empty() {
52            x.serialize_field("Facts", &self.facts)?;
53        }
54        x.end()
55    }
56}
57
58#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
59pub struct Fact {
60    pub attribute: Attribute,
61    pub value: Value,
62    pub facets: Vec<Facet>,
63    pub features: Vec<Feature>,
64    pub fields: Vec<Field>,
65    pub frames: Vec<Frame>,
66}
67
68#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
69pub struct Facet {
70    pub name: String255,
71    pub value: Value,
72    pub factors: Vec<Factor>,
73}
74
75#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
76pub struct Factor {
77    pub key: String255,
78    pub value: Value,
79}
80
81#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
82pub struct Feature {
83    pub name: String255,
84    pub key: String255,
85    pub value: Value,
86}
87
88#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
89pub struct Field {
90    pub name: String255,
91    pub default_value: Value,
92}
93
94#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
95pub struct Frame {
96    pub key: String255,
97    pub values: Vec<Value>,
98}
99
100#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
101pub struct Property {
102    pub name: String255,
103    pub key: String255,
104    pub value: Value,
105    #[serde(default, skip_serializing_if = "HashMap::is_empty")]
106    pub annotations: HashMap<Attribute, Value>,
107}
108
109#[cfg(test)]
110#[allow(clippy::approx_constant)]
111mod test {
112    use std::str::FromStr;
113
114    use ascii::{AsciiChar, AsciiString};
115    use maplit::hashmap;
116    use serde_json::json;
117    use time::macros::datetime;
118
119    use crate::{AvialError, Entity, Locutor, String255, Token, UnimplementedValue};
120
121    use super::*;
122
123    fn _test_ok(jsonobj: serde_json::Value, expected: AvialModel) {
124        let actual: AvialModel = match serde_json::from_value(jsonobj.clone()) {
125            Ok(v) => v,
126            Err(e) => {
127                panic!("Error deserializing: {}", e);
128            }
129        };
130        assert_eq!(actual, expected);
131
132        let ser = match serde_json::to_value(&expected) {
133            Ok(v) => v,
134            Err(e) => {
135                panic!("Error serializing: {}", e);
136            }
137        };
138        let actual_2: AvialModel = match serde_json::from_value(ser) {
139            Ok(v) => v,
140            Err(e) => {
141                panic!("Error deserializing again: {}", e);
142            }
143        };
144        assert_eq!(actual_2, expected);
145    }
146
147    fn _test_err(jsonobj: serde_json::Value) {
148        match serde_json::from_value::<AvialModel>(jsonobj.clone()) {
149            Ok(v) => {
150                panic!("Expected error from {jsonobj}, got: {:?}", v);
151            }
152            Err(e) => println!("OK: got expected error: {}", e),
153        }
154    }
155
156    #[test]
157    fn empty() {
158        _test_ok(
159            json!({}),
160            AvialModel {
161                name: String255::unchecked(""),
162                key: String255::unchecked(""),
163                data: 0,
164                properties: vec![],
165                facts: vec![],
166            },
167        );
168    }
169
170    #[test]
171    fn only_name() {
172        _test_ok(
173            json!(
174                {
175                    "Name": "test name"
176                }
177            ),
178            AvialModel {
179                name: String255::unchecked("test name"),
180                key: String255::unchecked(""),
181                data: 0,
182                properties: vec![],
183                facts: vec![],
184            },
185        );
186    }
187
188    #[test]
189    fn only_key() {
190        _test_ok(
191            json!(
192                {
193                    "Key": "test key"
194                }
195            ),
196            AvialModel {
197                name: String255::unchecked(""),
198                key: String255::unchecked("test key"),
199                data: 0,
200                properties: vec![],
201                facts: vec![],
202            },
203        );
204    }
205
206    #[test]
207    fn only_data() {
208        _test_ok(
209            json!(
210                {
211                    "Data": 123
212                }
213            ),
214            AvialModel {
215                name: String255::unchecked(""),
216                key: String255::unchecked(""),
217                data: 123,
218                properties: vec![],
219                facts: vec![],
220            },
221        );
222    }
223
224    #[test]
225    fn only_properties() {
226        _test_ok(
227            json!(
228            {
229                "Properties": [
230                    ["name1", "key1", { "INTEGER": "123" }],
231                ]
232            }),
233            AvialModel {
234                name: String255::unchecked(""),
235                key: String255::unchecked(""),
236                data: 0,
237                properties: vec![Property {
238                    name: String255::unchecked("name1"),
239                    key: String255::unchecked("key1"),
240                    value: Value::Integer(123),
241                    annotations: HashMap::new(),
242                }],
243                facts: vec![],
244            },
245        )
246    }
247
248    #[test]
249    fn invalid_property() {
250        _test_err(json!({"Properties": 123}));
251        _test_err(json!({"Properties": [ ["name1", "key1", { "INTEGER": "123" }, "toto" ] ]}));
252        _test_err(json!({"Properties": [ ["name1", "key1", "abc", {} ] ]}));
253        _test_err(json!({"Properties": [ ["name1", "key1", [], {} ] ]}));
254        _test_err(json!({"Properties": [ ["name1", "key1", {}, {} ] ]}));
255        _test_err(json!({"Properties": [ ["name1", "key1",  ] ]}));
256        _test_err(
257            json!({"Properties": [ ["name1", "key1", { "INTEGER": "123" }, {}, "extra field yayy" ] ]}),
258        );
259    }
260
261    #[test]
262    fn example_entity() {
263        _test_ok(
264            serde_json::from_str(include_str!("../test_json/example_entity_modified.json"))
265                .expect("Failed to parse example json file"),
266            AvialModel {
267                name: String255::unchecked("Example Entity"),
268                key: String255::unchecked("Example"),
269                data: 28,
270                properties: vec![
271                    Property {
272                        name: String255::unchecked("Null Property"),
273                        key: String255::unchecked("Null"),
274                        value: Value::Null("".to_string()),
275                        annotations: HashMap::new(),
276                    },
277                    Property {
278                        name: String255::unchecked("AvesTerra Property"),
279                        key: String255::unchecked("AvesTerra"),
280                        value: Value::Avesterra("Example".to_string()),
281                        annotations: HashMap::new(),
282                    },
283                    Property {
284                        name: String255::unchecked("Entity Property"),
285                        key: String255::unchecked("Entity"),
286                        value: Value::Entity(Entity::new(0, 0, 24)),
287                        annotations: HashMap::new(),
288                    },
289                    Property {
290                        name: String255::unchecked("Boolean Property"),
291                        key: String255::unchecked("Boolean"),
292                        value: Value::Boolean(true),
293                        annotations: HashMap::new(),
294                    },
295                    Property {
296                        name: String255::unchecked("Character Property"),
297                        key: String255::unchecked("Character"),
298                        value: Value::Character(AsciiChar::A),
299                        annotations: HashMap::new(),
300                    },
301                    Property {
302                        name: String255::unchecked("String Property"),
303                        key: String255::unchecked("String"),
304                        value: Value::String(
305                            AsciiString::from_ascii("Hello World!\u{0007}").unwrap(),
306                        ),
307                        annotations: HashMap::new(),
308                    },
309                    Property {
310                        name: String255::unchecked("Text Property"),
311                        key: String255::unchecked("Text"),
312                        value: Value::Text("ÅvësTêrrã".to_string()),
313                        annotations: HashMap::new(),
314                    },
315                    Property {
316                        name: String255::unchecked("Web Property"),
317                        key: String255::unchecked("Web"),
318                        value: Value::Web("www.example.com".to_string()),
319                        annotations: HashMap::new(),
320                    },
321                    Property {
322                        name: String255::unchecked("Interchange Property"),
323                        key: String255::unchecked("Interchange"),
324                        value: Value::Interchange(
325                            r#"{"Greeting":"Hello World!\u0007"}"#.to_string(),
326                        ),
327                        annotations: HashMap::new(),
328                    },
329                    Property {
330                        name: String255::unchecked("Integer Property"),
331                        key: String255::unchecked("Integer"),
332                        value: Value::Integer(42),
333                        annotations: HashMap::new(),
334                    },
335                    Property {
336                        name: String255::unchecked("Float Property"),
337                        key: String255::unchecked("Float"),
338                        value: Value::Float(3.14159),
339                        annotations: HashMap::new(),
340                    },
341                    Property {
342                        name: String255::unchecked("Time Property"),
343                        key: String255::unchecked("Time"),
344                        value: Value::Time(datetime!(2010-01-11 05:00:22 UTC)),
345                        annotations: HashMap::new(),
346                    },
347                    Property {
348                        name: String255::unchecked("Data Property"),
349                        key: String255::unchecked("Data"),
350                        value: Value::Data(vec![
351                            0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61,
352                            0x20, 0x7E, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29,
353                            0x5F, 0x2B, 0x22, 0x27,
354                        ]),
355                        annotations: HashMap::new(),
356                    },
357                    Property {
358                        name: String255::unchecked("Exception Property"),
359                        key: String255::unchecked("Exception"),
360                        value: Value::Exception(AvialError {
361                            error: Error::Halt,
362                            message: "The machine has halted".to_string(),
363                        }),
364                        annotations: HashMap::new(),
365                    },
366                    Property {
367                        name: String255::unchecked("Operator Property"),
368                        key: String255::unchecked("Operator"),
369                        value: Value::Operator(Operator::Halt),
370                        annotations: HashMap::new(),
371                    },
372                    Property {
373                        name: String255::unchecked("Function Property"),
374                        key: String255::unchecked("Function"),
375                        value: Value::Function(Entity::new(0, 0, 18)),
376                        annotations: HashMap::new(),
377                    },
378                    Property {
379                        name: String255::unchecked("Date Property"),
380                        key: String255::unchecked("Date"),
381                        value: Value::Date(UnimplementedValue(
382                            "{\"YEAR\": 1960,\"MONTH\": 10,\"DAY\": 1}".to_string(),
383                        )),
384                        annotations: HashMap::new(),
385                    },
386                    Property {
387                        name: String255::unchecked("Measurement Property"),
388                        key: String255::unchecked("Measurement"),
389                        value: Value::Measurement(UnimplementedValue(
390                            "{\"FLOAT\": 4.20000000000000E+01,\"UNIT\": \"GRAM_UNIT\",\"PREFIX\": \"MICRO_PREFIX\",\"CONFIDENCE\": 9.99000015258789E+01,\"UNCERTAINTY\": 1.00000001490116E-01}".to_string(),
391                        )),
392                        annotations: HashMap::new(),
393                    },
394                    Property {
395                        name: String255::unchecked("Authorization Property"),
396                        key: String255::unchecked("Authorization"),
397                        value: Value::Authorization(
398                            Token::from_str("285bbdb0-4966-4047-9261-3e0ddd3f2fab").unwrap(),
399                        ),
400                        annotations: HashMap::new(),
401                    },
402                    Property {
403                        name: String255::unchecked("Variable Property"),
404                        key: String255::unchecked("Variable"),
405                        value: Value::Variable(
406                            "City Variable".to_string(), Box::new(Value::String(AsciiString::from_ascii(r"Washington, D.C.").unwrap()))
407                        ),
408                        annotations: HashMap::new(),
409                    },
410                    Property {
411                        name: String255::unchecked("Array Property"),
412                        key: String255::unchecked("Array"),
413                        value: Value::Array(vec![
414                            Value::Entity(Entity::new(0, 0, 24)),
415                            Value::Integer(42),
416                            Value::Array(vec![
417                                Value::Character(AsciiChar::A),
418                                Value::Boolean(true),
419                            ]),
420                            ]
421                        ),
422                        annotations: HashMap::new(),
423                    },
424                    Property {
425                        name: String255::unchecked("Aggregate Property"),
426                        key: String255::unchecked("Aggregate"),
427                        value: Value::Aggregate(hashmap! {
428                            "Boolean Variable".into() => Value::Boolean(true),
429                            "Integer Variable".into() => Value::Integer(42),
430                            "Array Variable".into() => Value::Array(vec![
431                                Value::Entity(Entity::new(0, 0, 24)),
432                                Value::Integer(42),
433                                Value::Array(vec![
434                                    Value::Character(AsciiChar::A),
435                                    Value::Boolean(true),
436                                ]),
437                            ]),
438                        }),
439                        annotations: HashMap::new(),
440                    },
441                    Property {
442                        name: String255::unchecked("Annotation Property"),
443                        key: String255::unchecked("Annotation"),
444                        value: Value::Null("".to_string()),
445                        annotations: {
446                            let mut map = HashMap::new();
447                            map.insert(
448                                Attribute::Example,
449                                Value::String(AsciiString::from_ascii("Annotation Value").unwrap()),
450                            );
451                            map
452                        },
453                    },
454                    Property {
455                        name: String255::unchecked("Locutor Property"),
456                        key: String255::unchecked("Locutor"),
457                        value: Value::Locutor(Box::new(Locutor{
458                            entity: Entity::new(0, 0, 24),
459                            outlet: Entity::new(0, 0, 11),
460                            auxiliary: Entity::new(0, 0, 1),
461                            ancillary: Entity::new(0, 0, 2),
462                            context: Context::Avesterra,
463                            category: Category::Avesterra,
464                            class: Class::Avesterra,
465                            method: Method::Avesterra,
466                            attribute: Attribute::Avesterra,
467                            instance: 1,
468                            name: String255::unchecked("Example Name"),
469                            value: Value::String(AsciiString::from_ascii("Example String").unwrap()),
470                            index: 1,
471                            count: 123,
472                            precedence: 8,
473                            parameter: -1,
474                            mode: Mode::Avesterra,
475                            event: Event::Avesterra,
476                            timeout: 60,
477                            aspect: Aspect::Avesterra,
478                            authority: Token::NOAUTH,
479                            ..Default::default()
480                        })),
481                        annotations: HashMap::new(),
482                    },
483                ],
484                facts: vec![Fact {
485                    attribute: Attribute::Example,
486                    value: Value::String(AsciiString::from_ascii("Fact Value").unwrap()),
487                    facets: vec![
488                        Facet {
489                            name: String255::unchecked("Facet 1 Name"),
490                            value: Value::String(AsciiString::from_ascii("Facet 1 Value").unwrap()),
491                            factors: vec![
492                                Factor {
493                                    key: String255::unchecked("Facet 1 Fact 1 Key"),
494                                    value: Value::String(
495                                        AsciiString::from_ascii("Facet 1 Fact 1 Value").unwrap(),
496                                    ),
497                                },
498                                Factor {
499                                    key: String255::unchecked("Facet 1 Fact 2 Key"),
500                                    value: Value::String(
501                                        AsciiString::from_ascii("Facet 1 Fact 2 Value").unwrap(),
502                                    ),
503                                },
504                            ],
505                        },
506                        Facet {
507                            name: String255::unchecked("Facet 2 Name"),
508                            value: Value::String(AsciiString::from_ascii("Facet 2 Value").unwrap()),
509                            factors: vec![
510                                Factor {
511                                    key: String255::unchecked("Facet 2 Fact 1 Key"),
512                                    value: Value::String(
513                                        AsciiString::from_ascii("Facet 2 Fact 1 Value").unwrap(),
514                                    ),
515                                },
516                                Factor {
517                                    key: String255::unchecked("Facet 2 Fact 2 Key"),
518                                    value: Value::String(
519                                        AsciiString::from_ascii("Facet 2 Fact 2 Value").unwrap(),
520                                    ),
521                                },
522                            ],
523                        },
524                    ],
525                    features: vec![
526                        Feature {
527                            name: String255::unchecked("Feature 1 Name"),
528                            key: String255::unchecked("Feature 1 Key"),
529                            value: Value::String(
530                                AsciiString::from_ascii("Feature 1 Value").unwrap(),
531                            ),
532                        },
533                        Feature {
534                            name: String255::unchecked("Feature 2 Name"),
535                            key: String255::unchecked("Feature 2 Key"),
536                            value: Value::String(
537                                AsciiString::from_ascii("Feature 2 Value").unwrap(),
538                            ),
539                        },
540                        Feature {
541                            name: String255::unchecked("Feature 3 Name"),
542                            key: String255::unchecked("Feature 3 Key"),
543                            value: Value::String(
544                                AsciiString::from_ascii("Feature 3 Value").unwrap(),
545                            ),
546                        },
547                    ],
548                    fields: vec![
549                        Field {
550                            name: String255::unchecked("Field 1 Name"),
551                            default_value: Value::String(
552                                AsciiString::from_ascii("Field 1 Default Value").unwrap(),
553                            ),
554                        },
555                        Field {
556                            name: String255::unchecked("Field 2 Name"),
557                            default_value: Value::String(
558                                AsciiString::from_ascii("Field 2 Default Value").unwrap(),
559                            ),
560                        },
561                        Field {
562                            name: String255::unchecked("Field 3 Name"),
563                            default_value: Value::String(
564                                AsciiString::from_ascii("Field 3 Default Value").unwrap(),
565                            ),
566                        },
567                    ],
568                    frames: vec![
569                        Frame {
570                            key: String255::unchecked("Frame 1 Key"),
571                            values: vec![
572                                Value::String(
573                                    AsciiString::from_ascii("Frame 1 Field 1 Value").unwrap(),
574                                ),
575                                Value::String(
576                                    AsciiString::from_ascii("Frame 1 Field 2 Value").unwrap(),
577                                ),
578                                Value::String(
579                                    AsciiString::from_ascii("Frame 1 Field 3 Value").unwrap(),
580                                ),
581                            ],
582                        },
583                        Frame {
584                            key: String255::unchecked("Frame 2 Key"),
585                            values: vec![
586                                Value::String(
587                                    AsciiString::from_ascii("Frame 2 Field 1 Value").unwrap(),
588                                ),
589                                Value::String(
590                                    AsciiString::from_ascii("Frame 2 Field 2 Value").unwrap(),
591                                ),
592                                Value::String(
593                                    AsciiString::from_ascii("Frame 2 Field 3 Value").unwrap(),
594                                ),
595                            ],
596                        },
597                        Frame {
598                            key: String255::unchecked("Frame 3 Key"),
599                            values: vec![
600                                Value::String(
601                                    AsciiString::from_ascii("Frame 3 Field 1 Value").unwrap(),
602                                ),
603                                Value::String(
604                                    AsciiString::from_ascii("Frame 3 Field 2 Value").unwrap(),
605                                ),
606                                Value::String(
607                                    AsciiString::from_ascii("Frame 3 Field 3 Value").unwrap(),
608                                ),
609                            ],
610                        },
611                    ],
612                }],
613            },
614        )
615    }
616}