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