use serde::{ser::SerializeStruct, Deserialize, Serialize, Serializer};
use serde_tuple::{Deserialize_tuple, Serialize_tuple};
use std::collections::HashMap;
use crate::{taxonomy::*, String255, Value};
#[derive(Debug, Default, Clone, PartialEq, Deserialize)]
#[serde(default, rename_all = "PascalCase")]
pub struct AvialModel {
pub name: String255,
pub key: String255,
pub data: u64,
pub attributes: Vec<ModelAttribute>,
pub properties: Vec<ModelProperty>,
pub facts: Vec<ModelFact>,
}
impl Serialize for AvialModel {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
let mut x = serializer.serialize_struct("AvialModel", 4)?;
x.serialize_field("Model", "Avial")?;
if !self.name.is_empty() {
x.serialize_field("Name", &self.name)?;
}
if !self.key.is_empty() {
x.serialize_field("Key", &self.key)?;
}
if self.data > 0 {
x.serialize_field("Data", &self.data)?;
}
if !self.attributes.is_empty() {
x.serialize_field("Attributes", &self.attributes)?;
}
if !self.properties.is_empty() {
x.serialize_field("Properties", &self.properties)?;
}
if !self.facts.is_empty() {
x.serialize_field("Facts", &self.facts)?;
}
x.end()
}
}
#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct ModelAttribute {
pub attribute: Attribute,
pub value: Value,
pub traits: Vec<ModelTrait>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct ModelTrait {
pub name: String255,
pub key: String255,
pub value: Value,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct ModelFact {
pub attribute: Attribute,
pub value: Value,
pub facets: Vec<Facet>,
pub features: Vec<ModelFeature>,
pub fields: Vec<ModelField>,
pub frames: Vec<ModelFrame>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct Facet {
pub name: String255,
pub value: Value,
pub factors: Vec<ModelFactor>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct ModelFactor {
pub key: String255,
pub value: Value,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct ModelFeature {
pub name: String255,
pub key: String255,
pub value: Value,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct ModelField {
pub name: String255,
pub default_value: Value,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct ModelFrame {
pub key: String255,
pub values: Vec<Value>,
}
#[derive(Debug, Default, Clone, PartialEq, Serialize_tuple, Deserialize_tuple)]
pub struct ModelProperty {
pub name: String255,
pub key: String255,
pub value: Value,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub annotations: HashMap<Attribute, Value>,
}
#[cfg(test)]
#[allow(clippy::approx_constant)]
mod test {
use std::str::FromStr;
use ascii::{AsciiChar, AsciiString};
use maplit::hashmap;
use serde_json::json;
use time::macros::datetime;
use crate::{AvialError, Entity, Locutor, String255, Token, UnimplementedValue};
use super::*;
fn _test_ok(jsonobj: serde_json::Value, expected: AvialModel) {
let actual: AvialModel = match serde_json::from_value(jsonobj.clone()) {
Ok(v) => v,
Err(e) => {
panic!("Error deserializing: {}", e);
}
};
assert_eq!(actual, expected);
let ser = match serde_json::to_value(&expected) {
Ok(v) => v,
Err(e) => {
panic!("Error serializing: {}", e);
}
};
let actual_2: AvialModel = match serde_json::from_value(ser) {
Ok(v) => v,
Err(e) => {
panic!("Error deserializing again: {}", e);
}
};
assert_eq!(actual_2, expected);
}
fn _test_err(jsonobj: serde_json::Value) {
match serde_json::from_value::<AvialModel>(jsonobj.clone()) {
Ok(v) => {
panic!("Expected error from {jsonobj}, got: {:?}", v);
}
Err(e) => println!("OK: got expected error: {}", e),
}
}
#[test]
fn empty() {
_test_ok(
json!({}),
AvialModel {
name: String255::unchecked(""),
key: String255::unchecked(""),
data: 0,
attributes: vec![],
properties: vec![],
facts: vec![],
},
);
}
#[test]
fn only_name() {
_test_ok(
json!(
{
"Name": "test name"
}
),
AvialModel {
name: String255::unchecked("test name"),
key: String255::unchecked(""),
data: 0,
attributes: vec![],
properties: vec![],
facts: vec![],
},
);
}
#[test]
fn only_key() {
_test_ok(
json!(
{
"Key": "test key"
}
),
AvialModel {
name: String255::unchecked(""),
key: String255::unchecked("test key"),
data: 0,
attributes: vec![],
properties: vec![],
facts: vec![],
},
);
}
#[test]
fn only_data() {
_test_ok(
json!(
{
"Data": 123
}
),
AvialModel {
name: String255::unchecked(""),
key: String255::unchecked(""),
data: 123,
attributes: vec![],
properties: vec![],
facts: vec![],
},
);
}
#[test]
fn only_properties() {
_test_ok(
json!(
{
"Properties": [
["name1", "key1", { "INTEGER": "123" }],
]
}),
AvialModel {
name: String255::unchecked(""),
key: String255::unchecked(""),
data: 0,
attributes: vec![],
properties: vec![ModelProperty {
name: String255::unchecked("name1"),
key: String255::unchecked("key1"),
value: Value::Integer(123),
annotations: HashMap::new(),
}],
facts: vec![],
},
)
}
#[test]
fn invalid_property() {
_test_err(json!({"Properties": 123}));
_test_err(json!({"Properties": [ ["name1", "key1", { "INTEGER": "123" }, "toto" ] ]}));
_test_err(json!({"Properties": [ ["name1", "key1", "abc", {} ] ]}));
_test_err(json!({"Properties": [ ["name1", "key1", [], {} ] ]}));
_test_err(json!({"Properties": [ ["name1", "key1", {}, {} ] ]}));
_test_err(json!({"Properties": [ ["name1", "key1", ] ]}));
_test_err(
json!({"Properties": [ ["name1", "key1", { "INTEGER": "123" }, {}, "extra field yayy" ] ]}),
);
}
#[test]
fn example_entity() {
_test_ok(
serde_json::from_str(include_str!("../test_json/example_entity_modified.json"))
.expect("Failed to parse example json file"),
AvialModel {
name: String255::unchecked("Example Entity"),
key: String255::unchecked("Example"),
data: 28,
attributes: vec![
ModelAttribute {
attribute: Attribute::Example,
value: Value::String(AsciiString::from_ascii("Attribute Value").unwrap()),
traits: vec![
ModelTrait {
name: String255::unchecked("Trait 1 Name"),
key: String255::unchecked("Trait 1 Key"),
value: Value::String(AsciiString::from_ascii("Trait 1 Value").unwrap()),
},
ModelTrait {
name: String255::unchecked("Trait 2 Name"),
key: String255::unchecked("Trait 2 Key"),
value: Value::String(AsciiString::from_ascii("Trait 2 Value").unwrap()),
},
],
},
],
properties: vec![
ModelProperty {
name: String255::unchecked("Null Property"),
key: String255::unchecked("Null"),
value: Value::Null("".to_string()),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("AvesTerra Property"),
key: String255::unchecked("AvesTerra"),
value: Value::Avesterra("Example".to_string()),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Entity Property"),
key: String255::unchecked("Entity"),
value: Value::Entity(Entity::new(0, 0, 24)),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Boolean Property"),
key: String255::unchecked("Boolean"),
value: Value::Boolean(true),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Character Property"),
key: String255::unchecked("Character"),
value: Value::Character(AsciiChar::A),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("String Property"),
key: String255::unchecked("String"),
value: Value::String(
AsciiString::from_ascii("Hello World!\u{0007}").unwrap(),
),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Text Property"),
key: String255::unchecked("Text"),
value: Value::Text("ÅvësTêrrã".to_string()),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Web Property"),
key: String255::unchecked("Web"),
value: Value::Web("www.example.com".to_string()),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Interchange Property"),
key: String255::unchecked("Interchange"),
value: Value::Interchange(
r#"{"Greeting":"Hello World!\u0007"}"#.to_string(),
),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Integer Property"),
key: String255::unchecked("Integer"),
value: Value::Integer(42),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Float Property"),
key: String255::unchecked("Float"),
value: Value::Float(3.14159),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Time Property"),
key: String255::unchecked("Time"),
value: Value::Time(datetime!(2010-01-11 05:00:22 UTC)),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Data Property"),
key: String255::unchecked("Data"),
value: Value::Data(vec![
0x45, 0x78, 0x61, 0x6D, 0x70, 0x6C, 0x65, 0x20, 0x44, 0x61, 0x74, 0x61,
0x20, 0x7E, 0x21, 0x40, 0x23, 0x24, 0x25, 0x5E, 0x26, 0x2A, 0x28, 0x29,
0x5F, 0x2B, 0x22, 0x27,
]),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Exception Property"),
key: String255::unchecked("Exception"),
value: Value::Exception(AvialError {
error: Error::Halt,
message: "The machine has halted".to_string(),
}),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Operator Property"),
key: String255::unchecked("Operator"),
value: Value::Operator(Operator::Halt),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Function Property"),
key: String255::unchecked("Function"),
value: Value::Function(Entity::new(0, 0, 18)),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Date Property"),
key: String255::unchecked("Date"),
value: Value::Date(UnimplementedValue(
"{\"YEAR\": 1960,\"MONTH\": 10,\"DAY\": 1}".to_string(),
)),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Measurement Property"),
key: String255::unchecked("Measurement"),
value: Value::Measurement(UnimplementedValue(
"{\"FLOAT\": 4.20000000000000E+01,\"UNIT\": \"GRAM_UNIT\",\"PREFIX\": \"MICRO_PREFIX\",\"CONFIDENCE\": 9.99000015258789E+01,\"UNCERTAINTY\": 1.00000001490116E-01}".to_string(),
)),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Authorization Property"),
key: String255::unchecked("Authorization"),
value: Value::Authorization(
Token::from_str("285bbdb0-4966-4047-9261-3e0ddd3f2fab").unwrap(),
),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Variable Property"),
key: String255::unchecked("Variable"),
value: Value::Variable(
"City Variable".to_string(), Box::new(Value::String(AsciiString::from_ascii(r"Washington, D.C.").unwrap()))
),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Array Property"),
key: String255::unchecked("Array"),
value: Value::Array(vec![
Value::Entity(Entity::new(0, 0, 24)),
Value::Integer(42),
Value::Array(vec![
Value::Character(AsciiChar::A),
Value::Boolean(true),
]),
]
),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Aggregate Property"),
key: String255::unchecked("Aggregate"),
value: Value::Aggregate(hashmap! {
"Boolean Variable".into() => Value::Boolean(true),
"Integer Variable".into() => Value::Integer(42),
"Array Variable".into() => Value::Array(vec![
Value::Entity(Entity::new(0, 0, 24)),
Value::Integer(42),
Value::Array(vec![
Value::Character(AsciiChar::A),
Value::Boolean(true),
]),
]),
}),
annotations: HashMap::new(),
},
ModelProperty {
name: String255::unchecked("Annotation Property"),
key: String255::unchecked("Annotation"),
value: Value::Null("".to_string()),
annotations: {
let mut map = HashMap::new();
map.insert(
Attribute::Example,
Value::String(AsciiString::from_ascii("Annotation Value").unwrap()),
);
map
},
},
ModelProperty {
name: String255::unchecked("Locutor Property"),
key: String255::unchecked("Locutor"),
value: Value::Locutor(Box::new(Locutor{
entity: Entity::new(0, 0, 24),
outlet: Entity::new(0, 0, 11),
auxiliary: Entity::new(0, 0, 1),
ancillary: Entity::new(0, 0, 2),
context: Context::Avesterra,
category: Category::Avesterra,
class: Class::Avesterra,
method: Method::Avesterra,
attribute: Attribute::Avesterra,
instance: 1,
name: String255::unchecked("Example Name"),
value: Value::String(AsciiString::from_ascii("Example String").unwrap()),
index: 1,
count: 123,
precedence: 8,
parameter: -1,
mode: Mode::Avesterra,
event: Event::Avesterra,
timeout: 60,
aspect: Aspect::Avesterra,
authority: Token::NOAUTH,
..Default::default()
})),
annotations: HashMap::new(),
},
],
facts: vec![ModelFact {
attribute: Attribute::Example,
value: Value::String(AsciiString::from_ascii("Fact Value").unwrap()),
facets: vec![
Facet {
name: String255::unchecked("Facet 1 Name"),
value: Value::String(AsciiString::from_ascii("Facet 1 Value").unwrap()),
factors: vec![
ModelFactor {
key: String255::unchecked("Facet 1 Fact 1 Key"),
value: Value::String(
AsciiString::from_ascii("Facet 1 Fact 1 Value").unwrap(),
),
},
ModelFactor {
key: String255::unchecked("Facet 1 Fact 2 Key"),
value: Value::String(
AsciiString::from_ascii("Facet 1 Fact 2 Value").unwrap(),
),
},
],
},
Facet {
name: String255::unchecked("Facet 2 Name"),
value: Value::String(AsciiString::from_ascii("Facet 2 Value").unwrap()),
factors: vec![
ModelFactor {
key: String255::unchecked("Facet 2 Fact 1 Key"),
value: Value::String(
AsciiString::from_ascii("Facet 2 Fact 1 Value").unwrap(),
),
},
ModelFactor {
key: String255::unchecked("Facet 2 Fact 2 Key"),
value: Value::String(
AsciiString::from_ascii("Facet 2 Fact 2 Value").unwrap(),
),
},
],
},
],
features: vec![
ModelFeature {
name: String255::unchecked("Feature 1 Name"),
key: String255::unchecked("Feature 1 Key"),
value: Value::String(
AsciiString::from_ascii("Feature 1 Value").unwrap(),
),
},
ModelFeature {
name: String255::unchecked("Feature 2 Name"),
key: String255::unchecked("Feature 2 Key"),
value: Value::String(
AsciiString::from_ascii("Feature 2 Value").unwrap(),
),
},
ModelFeature {
name: String255::unchecked("Feature 3 Name"),
key: String255::unchecked("Feature 3 Key"),
value: Value::String(
AsciiString::from_ascii("Feature 3 Value").unwrap(),
),
},
],
fields: vec![
ModelField {
name: String255::unchecked("Field 1 Name"),
default_value: Value::String(
AsciiString::from_ascii("Field 1 Default Value").unwrap(),
),
},
ModelField {
name: String255::unchecked("Field 2 Name"),
default_value: Value::String(
AsciiString::from_ascii("Field 2 Default Value").unwrap(),
),
},
ModelField {
name: String255::unchecked("Field 3 Name"),
default_value: Value::String(
AsciiString::from_ascii("Field 3 Default Value").unwrap(),
),
},
],
frames: vec![
ModelFrame {
key: String255::unchecked("Frame 1 Key"),
values: vec![
Value::String(
AsciiString::from_ascii("Frame 1 Field 1 Value").unwrap(),
),
Value::String(
AsciiString::from_ascii("Frame 1 Field 2 Value").unwrap(),
),
Value::String(
AsciiString::from_ascii("Frame 1 Field 3 Value").unwrap(),
),
],
},
ModelFrame {
key: String255::unchecked("Frame 2 Key"),
values: vec![
Value::String(
AsciiString::from_ascii("Frame 2 Field 1 Value").unwrap(),
),
Value::String(
AsciiString::from_ascii("Frame 2 Field 2 Value").unwrap(),
),
Value::String(
AsciiString::from_ascii("Frame 2 Field 3 Value").unwrap(),
),
],
},
ModelFrame {
key: String255::unchecked("Frame 3 Key"),
values: vec![
Value::String(
AsciiString::from_ascii("Frame 3 Field 1 Value").unwrap(),
),
Value::String(
AsciiString::from_ascii("Frame 3 Field 2 Value").unwrap(),
),
Value::String(
AsciiString::from_ascii("Frame 3 Field 3 Value").unwrap(),
),
],
},
],
}],
},
)
}
}