/* Copyright 2024-2025 LEDR Technologies Inc.
* 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.
*
* 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.
*
* 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.
*
* 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/>.
*
* If you have any questions, feedback or issues about the Orchestra library, you can contact us at support@ledr.io.
*/
use crate::time_serde::*;
use crate::AvialError;
use crate::Locutor;
use crate::Operator;
use crate::{Entity, Tag, Token};
use ascii::AsciiChar;
use ascii::AsciiString;
use ascii::IntoAsciiString;
use std::collections::HashMap;
use std::num::IntErrorKind;
use std::{cmp::PartialEq, fmt, str::FromStr};
use thiserror::Error;
use time::OffsetDateTime;
// Used for value variant that isn't implemented
#[derive(Clone, Debug, PartialEq)]
pub struct UnimplementedValue(pub String);
#[derive(Clone, PartialEq, Debug)]
pub enum Value {
Null(String),
Avesterra(String),
Boolean(bool),
Character(AsciiChar),
String(AsciiString),
Text(String),
Integer(i64),
Float(f64),
Entity(Entity),
Time(OffsetDateTime),
Web(String),
Interchange(String),
Data(Vec<u8>),
Exception(AvialError),
Operator(Operator),
Function(Entity),
Measurement(UnimplementedValue),
Locutor(Box<Locutor>),
Authorization(Token),
Date(UnimplementedValue),
Aggregate(HashMap<String, Value>),
Array(Vec<Value>),
Variable(String, Box<Value>),
}
impl Value {
pub const NULL: Value = Value::Null(String::new());
pub fn is_null(&self) -> bool {
matches!(self, Value::Null(_))
}
}
#[derive(Error, Debug)]
#[error("Value of type {actual:?} cannot be read as {expected:?}")]
pub struct ConvertValueError {
expected: Tag,
actual: Tag,
}
#[derive(Error, Debug)]
#[error("Fail to create value of type {tag:?} from string {string:?}: {error}")]
pub struct ValueCreationError {
tag: Tag,
string: String,
error: Box<dyn std::error::Error + std::marker::Sync + std::marker::Send>,
}
impl Default for Value {
fn default() -> Self {
Self::Null(String::new())
}
}
macro_rules! getter {
($name:ident, $tag:ident, $type:ty) => {
pub fn $name(self) -> Result<$type, ConvertValueError> {
match self {
Value::$tag(val) => Ok(val),
_ => Err(ConvertValueError {
expected: Tag::$tag,
actual: self.get_tag(),
}),
}
}
};
}
impl Value {
pub fn get_tag(&self) -> Tag {
match *self {
Value::Null(_) => Tag::Null,
Value::Avesterra(_) => Tag::Avesterra,
Value::Boolean(_) => Tag::Boolean,
Value::Character(_) => Tag::Character,
Value::String(_) => Tag::String,
Value::Text(_) => Tag::Text,
Value::Integer(_) => Tag::Integer,
Value::Float(_) => Tag::Float,
Value::Entity(_) => Tag::Entity,
Value::Time(_) => Tag::Time,
Value::Web(_) => Tag::Web,
Value::Interchange(_) => Tag::Interchange,
Value::Data(_) => Tag::Data,
Value::Exception(_) => Tag::Exception,
Value::Operator(_) => Tag::Operator,
Value::Function(_) => Tag::Function,
Value::Measurement(_) => Tag::Measurement,
Value::Locutor(_) => Tag::Locutor,
Value::Authorization(_) => Tag::Authorization,
Value::Date(_) => Tag::Date,
Value::Aggregate(_) => Tag::Aggregate,
Value::Array(_) => Tag::Array,
Value::Variable(_, _) => Tag::Variable,
}
}
getter!(null, Null, String);
getter!(avesterra, Avesterra, String);
getter!(boolean, Boolean, bool);
getter!(character, Character, AsciiChar);
getter!(string, String, AsciiString);
getter!(text, Text, String);
getter!(integer, Integer, i64);
getter!(float, Float, f64);
getter!(entity, Entity, Entity);
getter!(time, Time, OffsetDateTime);
getter!(web, Web, String);
getter!(interchange, Interchange, String);
getter!(data, Data, Vec<u8>);
getter!(exception, Exception, AvialError);
getter!(operator, Operator, Operator);
getter!(function, Function, Entity);
// getter!(measurement, Measurement, Measurement);
getter!(locutor, Locutor, Box<Locutor>);
getter!(authorization, Authorization, Token);
// getter!(date, Date, UnimplementedValue);
getter!(aggregate, Aggregate, HashMap<String, Value>);
getter!(array, Array, Vec<Value>);
pub fn variable(self) -> Result<(String, Box<Value>), ConvertValueError> {
match self {
Value::Variable(name, val) => Ok((name, val)),
_ => Err(ConvertValueError {
expected: Tag::Variable,
actual: self.get_tag(),
}),
}
}
pub(crate) fn new(tag: Tag, s: String) -> Result<Value, ValueCreationError> {
let result = match tag {
Tag::Null => Value::Null(s),
Tag::Avesterra => Value::Avesterra(s),
Tag::Boolean => Value::Boolean(bool::from_str(&s.to_lowercase()).map_err(|_| {
ValueCreationError {
tag,
string: s.to_owned(),
error: "provided string was not `true` or `false` (case insensitive)".into(),
}
})?),
Tag::Character => {
let mut b = s.bytes();
let char = match (b.next(), b.next()) {
(Some(b), None) => AsciiChar::from_ascii(b).map_err(|e| ValueCreationError {
tag,
string: s.to_owned(),
error: Box::new(e),
}),
_ => Err(ValueCreationError {
tag,
string: s.to_owned(),
error: "ascii character is not 1 byte".into(),
}),
};
Value::Character(char?)
}
Tag::String => {
Value::String(s.into_ascii_string().map_err(|e| ValueCreationError {
tag,
string: e.clone().into_source(),
error: Box::new(e),
})?)
}
Tag::Text => Value::Text(s),
Tag::Integer => Value::Integer(i64::from_str(&s).map_err(|e| ValueCreationError {
tag,
string: s.to_owned(),
error: match e.kind() {
IntErrorKind::PosOverflow | IntErrorKind::NegOverflow => {
"integer out of range, value does not fit in 64 bytes signed integer".into()
}
_ => Box::new(e),
},
})?),
Tag::Float => Value::Float(f64::from_str(&s).map_err(|e| ValueCreationError {
tag,
string: s.to_owned(),
error: Box::new(e),
})?),
Tag::Entity => {
Value::Entity(Entity::from_str_strict(&s).map_err(|e| ValueCreationError {
tag,
string: s.to_owned(),
error: Box::new(e),
})?)
}
Tag::Time => {
Value::Time(
unix_timestamp_string_to_time(&s).map_err(|e| ValueCreationError {
tag,
string: s.to_owned(),
error: Box::new(e),
})?,
)
}
Tag::Web => Value::Web(s),
Tag::Interchange => Value::Interchange(s),
Tag::Data => Value::Data(base16::decode(&s).map_err(|e| ValueCreationError {
tag,
string: s.to_owned(),
error: Box::new(e),
})?),
Tag::Exception => {
Value::Exception(serde_json::from_str(&s).map_err(|e| ValueCreationError {
tag,
string: s.to_owned(),
error: Box::new(e),
})?)
}
Tag::Operator => Value::Operator(
serde_json::from_value(serde_json::Value::String(s.clone())).map_err(|e| {
ValueCreationError {
tag,
string: s.to_owned(),
error: Box::new(e),
}
})?,
),
Tag::Function => {
Value::Function(Entity::from_str_strict(&s).map_err(|e| ValueCreationError {
tag,
string: s.to_owned(),
error: Box::new(e),
})?)
}
Tag::Measurement => Value::Measurement(UnimplementedValue(s)),
Tag::Locutor => Value::Locutor(Box::new(serde_json::from_str(&s).map_err(|e| {
ValueCreationError {
tag,
string: s.to_owned(),
error: Box::new(e),
}
})?)),
Tag::Authorization => {
Value::Authorization(Token::from_str(&s).map_err(|e| ValueCreationError {
tag,
string: s.to_owned(),
error: Box::new(e),
})?)
}
Tag::Date => Value::Date(UnimplementedValue(s)),
Tag::Aggregate => {
Value::Aggregate(serde_json::from_str(&s).map_err(|e| ValueCreationError {
tag,
string: s.to_owned(),
error: Box::new(e),
})?)
}
Tag::Array => {
Value::Array(serde_json::from_str(&s).map_err(|e| ValueCreationError {
tag,
string: s.to_owned(),
error: Box::new(e),
})?)
}
Tag::Variable => {
let map: HashMap<String, Value> =
serde_json::from_str(&s).map_err(|e| ValueCreationError {
tag,
string: s.to_owned(),
error: Box::new(e),
})?;
if map.len() != 1 {
return Err(ValueCreationError {
tag,
string: s.to_owned(),
error: "variable must have exactly one key".into(),
});
}
let kvp = map.into_iter().next().unwrap();
Value::Variable(kvp.0, Box::new(kvp.1))
}
};
Ok(result)
}
#[rustfmt::skip]
/// Gives the string representation of the value, as encoded in JSON
pub(crate) fn get_bytes(&self) -> String {
// UNWRAP SAFETY:
// serde_json::to_string documentation states:
//
// > Serialization can fail if `T`'s implementation of `Serialize` decides to
// > fail, or if `T` contains a map with non-string keys.
//
// We control both the implementation of `Serialize` for every item,
// and the keys of the maps, so we can be sure that this will never fail.
match self {
Value::Null(v) => v.to_owned(),
Value::Avesterra(v) => v.to_owned(),
Value::Boolean(v) => v.to_string().to_ascii_uppercase(),
Value::Character(v) => v.as_char().to_string(),
Value::String(v) => v.to_string(),
Value::Text(v) => v.to_owned(),
Value::Integer(v) => v.to_string(),
Value::Float(v) => v.to_string(),
Value::Entity(v) => v.to_string(),
Value::Time(v) => time_to_unix_timestamp_string(v),
Value::Web(v) => v.to_string(),
Value::Interchange(v) => v.to_owned(), // TODO: serialize avialModel
Value::Data(v) => base16::encode_upper(v),
Value::Exception(v) => serde_json::to_string(v).unwrap(),
Value::Operator(v) => serde_json::to_value(v).unwrap().as_str().unwrap().to_owned(),
Value::Function(v) => v.to_string(),
Value::Measurement(v) => v.0.clone(),
Value::Locutor(v) => serde_json::to_string(v).unwrap(),
Value::Authorization(v) => v.to_string(),
Value::Date(v) => v.0.clone(),
Value::Aggregate(v) => serde_json::to_string(v).unwrap(),
Value::Array(v) => serde_json::to_string(v).unwrap(),
Value::Variable(n, v) => serde_json::json!{ { n: v } }.to_string()
}
}
}
mod serde_impl {
use super::*;
use serde::de;
use serde::ser;
use serde::ser::SerializeMap;
use crate::Tag;
impl ser::Serialize for Value {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: ser::Serializer,
{
let tag = self.get_tag();
let value = self.get_bytes();
// For some reasons, a value json is the only place where tags are
// written without the _TAG suffix
let tagstr = serde_json::to_value(tag)
.unwrap()
.as_str()
.unwrap()
.strip_suffix("_TAG")
.unwrap()
.to_owned();
let mut state = serializer.serialize_map(Some(1))?;
state.serialize_entry(&tagstr, &value)?;
state.end()
}
}
struct ValueVisitor;
impl<'de> de::Visitor<'de> for ValueVisitor {
type Value = crate::Value;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("a tag value pair")
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where
A: de::MapAccess<'de>,
{
let (tag, value) = match (
map.next_entry::<Tag, String>()?,
map.next_entry::<String, String>()?,
) {
(Some((tag, value)), None) => (tag, value),
(None, None) => return Err(serde::de::Error::custom("missing tag/value pair")),
(Some(_), Some(_)) => {
return Err(serde::de::Error::custom("expected only one tag/value pair"))
}
(None, Some(_)) => unreachable!(),
};
Value::new(tag, value).map_err(serde::de::Error::custom)
}
}
impl<'de> de::Deserialize<'de> for crate::Value {
fn deserialize<D>(deserializer: D) -> Result<crate::Value, D::Error>
where
D: de::Deserializer<'de>,
{
deserializer.deserialize_map(ValueVisitor)
}
}
}
impl fmt::Display for Value {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let tag = self.get_tag();
let val = self.get_bytes();
write!(f, "Value({tag:?}, {val})")
}
}
#[cfg(test)]
mod test {
use ascii::AsciiString;
use maplit::hashmap;
use serde_json::json;
use time::macros::datetime;
use super::*;
use crate::taxonomy::*;
use crate::AvialError;
use crate::String255;
macro_rules! test_ok {
($value:expr, $json:expr $(,)?) => {
let jsonval = $json;
let v: Value = match serde_json::from_value(jsonval.clone()) {
Ok(v) => v,
Err(e) => panic!("unexpected error parsing json: {:?}", e),
};
assert_eq!(v, $value);
let endval = match serde_json::to_value(v) {
Ok(v) => v,
Err(e) => panic!("unexpected error serializing value: {:?}", e),
};
assert_eq!(endval, jsonval);
};
}
macro_rules! test_ok2 {
($value:expr, $json_in:expr, $json_out:expr $(,)?) => {
let jsonval = $json_in;
let v: Value = match serde_json::from_value(jsonval.clone()) {
Ok(v) => v,
Err(e) => panic!("unexpected error parsing json: {:?}", e),
};
assert_eq!(v, $value);
let endval = match serde_json::to_value(v) {
Ok(v) => v,
Err(e) => panic!("unexpected error serializing value: {:?}", e),
};
let jsonval2 = $json_out;
assert_eq!(endval, jsonval2);
};
}
// Made for value where we can't check the exact json output
macro_rules! test_ok_nojsoncheck {
($value:expr, $json:expr $(,)?) => {
let jsonval = $json;
let v: Value = match serde_json::from_value(jsonval.clone()) {
Ok(v) => v,
Err(e) => panic!("unexpected error parsing json: {:?}", e),
};
assert_eq!(v, $value);
let endjsonval = match serde_json::to_value(v.clone()) {
Ok(v) => v,
Err(e) => panic!("unexpected error serializing value: {:?}", e),
};
let v2: Value = match serde_json::from_value(endjsonval.clone()) {
Ok(v) => v,
Err(e) => panic!(
"unexpected error parsing json after re-creating it: {:?}",
e
),
};
assert_eq!(v.clone(), v2, "value: {:?}, json: {:?}", v, endjsonval);
};
}
macro_rules! test_err {
($json:expr) => {
match serde_json::from_value::<Value>($json) {
Ok(v) => panic!("expected error, got {:?}", v),
Err(e) => println!("OK: got expected error: {}", e),
};
};
}
#[test]
fn general_errors() {
// TOFIX: test_err!(json!({ "STRING": "first", "STRING": "second" }));
test_err!(json!({}));
test_err!(json!({ "INTEGER": 42 })); // it should be "42"
test_err!(json!({ "BLAH": "Hi" }));
}
#[test]
fn null() {
test_ok!(Value::Null("".into()), json!({ "NULL": "" }));
test_ok!(
Value::Null("content im not sure is used anyways".into()),
json!({ "NULL": "content im not sure is used anyways" })
);
test_ok!(
Value::Null("你好世界".into()),
json!({ "NULL": "你好世界" })
);
}
#[test]
fn avesterra() {
test_ok!(Value::Avesterra("".into()), json!({ "AVESTERRA": "" }));
test_ok!(
Value::Avesterra("Example".into()),
json!({ "AVESTERRA": "Example" })
);
}
#[test]
fn boolean() {
test_ok!(Value::Boolean(true), json!({ "BOOLEAN": "TRUE" }));
test_ok!(Value::Boolean(false), json!({ "BOOLEAN": "FALSE" }));
test_err!(json!({ "BOOLEAN": "blah" }));
test_err!(json!({ "BOOLEAN": "" }));
}
#[test]
fn character() {
test_ok!(Value::Character(AsciiChar::a), json!({ "CHARACTER": "a" }));
test_ok!(Value::Character(AsciiChar::A), json!({ "CHARACTER": "A" }));
test_ok!(Value::Character(AsciiChar::_1), json!({ "CHARACTER": "1" }));
test_ok!(
Value::Character(AsciiChar::Space),
json!({ "CHARACTER": " " })
);
test_err!(json!({ "CHARACTER": "" }));
test_err!(json!({ "CHARACTER": "ab" }));
test_err!(json!({ "CHARACTER": "ä" }));
}
#[test]
fn string() {
test_ok!(Value::String(AsciiString::new()), json!({ "STRING": "" }));
test_ok!(
Value::String(AsciiString::from_ascii(r"Hello World!\u0007").unwrap()),
json!({ "STRING": r"Hello World!\u0007" })
);
test_err!(json!({ "STRING": "Hello world is 你好世界" }));
}
#[test]
fn text() {
test_ok!(Value::Text("".into()), json!({ "TEXT": "" }));
test_ok!(
Value::Text("ÅvësTêrrã".into()),
json!({ "TEXT": "ÅvësTêrrã" })
);
test_ok!(
Value::Text("Hello world is 你好世界".into()),
json!({ "TEXT": "Hello world is 你好世界" })
);
}
#[test]
fn integer() {
test_ok!(Value::Integer(0), json!({ "INTEGER": "0" }));
test_ok!(Value::Integer(42), json!({ "INTEGER": "42" }));
test_ok!(Value::Integer(-42), json!({ "INTEGER": "-42" }));
test_err!(json!({ "INTEGER": "42.0" }));
test_err!(json!({ "INTEGER": "" }));
test_err!(json!({ "INTEGER": "three" }));
test_ok!(
Value::Integer(9_223_372_036_854_775_807),
json!({ "INTEGER": "9223372036854775807" })
);
test_ok!(
Value::Integer(-9_223_372_036_854_775_808),
json!({ "INTEGER": "-9223372036854775808" })
);
test_err!(json!({ "INTEGER": "9223372036854775808" }));
test_err!(json!({ "INTEGER": "-9223372036854775809" }));
}
#[test]
#[allow(clippy::approx_constant)]
fn float() {
// Note: It's unclear what the standard is, it seems that every
// implementation is different.
test_ok2!(
Value::Float(3.14159),
json!({ "FLOAT":"3.14159000000000000E+00" }),
json!({ "FLOAT":"3.14159" })
);
test_err!(json!({ "FLOAT": "" }));
test_err!(json!({ "FLOAT": "hello" }));
}
#[test]
fn entity() {
test_ok!(
Value::Entity(Entity {
pid: 0,
hid: 0,
uid: 24
}),
json!({ "ENTITY": "<0|0|24>" })
);
test_ok!(
Value::Entity(Entity {
pid: 0,
hid: 0,
uid: 24
}),
json!({ "ENTITY": "<0|0|24>" })
);
test_err!(json!({ "ENTITY": "" }));
test_err!(json!({ "ENTITY": "<0|0|>" }));
test_err!(json!({ "ENTITY": "<0|0|24" }));
test_err!(json!({ "ENTITY": "<0|0|24|>" }));
test_err!(json!({ "ENTITY": "<0|0|24|24>" }));
test_err!(json!({ "ENTITY": "0|0|24>" }));
// This is not standard and shouldn't be allowed
// except locally, where which server is referred to is obvious from context
test_err!(json!({ "ENTITY": "<24>" }));
}
#[test]
fn time() {
test_ok!(
Value::Time(datetime!(1970-01-01 00:00:00 UTC)),
json!({ "TIME": "0" })
);
test_ok!(
Value::Time(datetime!(2010-01-11 00:00:22 UTC)),
json!({ "TIME": "1263168022" })
);
test_ok!(
Value::Time(datetime!(1895-07-15 09:25:51 UTC)),
json!({ "TIME": "-2349873249" })
);
test_err!(json!({ "TIME": "" }));
test_err!(json!({ "TIME": "안녕" }));
test_err!(json!({ "TIME": "9223372036854775808" })); // i64 overflow
test_err!(json!({ "TIME": "9223372036854775807" })); // too far in the future
}
#[test]
fn web() {
test_ok!(
Value::Web("https://example.com".into()),
json!({ "WEB": "https://example.com" }),
);
test_ok!(
Value::Web("https://example.com/%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C".into()),
json!({ "WEB": "https://example.com/%E4%BD%A0%E5%A5%BD%E4%B8%96%E7%95%8C" }),
);
test_ok!(
Value::Web("www.example.com".into()),
json!({ "WEB": "www.example.com" })
);
// Not sure if we should allow this :(
// There is no standard for this, better leave that to the application layer.
test_ok!(Value::Web("".into()), json!({ "WEB": "" }));
}
#[test]
fn interchange() {} // TODO: AvialModel
#[test]
fn data() {
test_ok!(
Value::Data(vec![0x00, 0x01, 0x02, 0x03, 0x04, 0x05]),
json!({ "DATA": "000102030405" })
);
test_ok!(
Value::Data(vec![0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F]),
json!({ "DATA": "0A0B0C0D0E0F" })
);
test_ok2!(
Value::Data(vec![0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F]),
json!({ "DATA": "0a0b0c0d0e0f" }),
json!({ "DATA": "0A0B0C0D0E0F" }),
);
test_ok!(
Value::Data(String::from(r#"Example Data ~!@#$%^&*()_+"'"#).into_bytes()),
json!({ "DATA": "4578616D706C652044617461207E21402324255E262A28295F2B2227" })
);
test_ok!(Value::Data(vec![]), json!({ "DATA": "" }));
test_err!(json!({ "DATA": "0G" }));
test_err!(json!({ "DATA": ".f" }));
test_err!(json!({ "DATA": "3" }));
}
#[test]
fn exception() {
test_ok!(
Value::Exception(AvialError {
error: Error::Halt,
message: "The machine has halted".into()
}),
json!({ "EXCEPTION": r#"{"ERROR":"HALT_ERROR","MESSAGE":"The machine has halted"}"# })
);
test_ok!(
Value::Exception(AvialError {
error: Error::Adapter,
message: "There was an error".into()
}),
json!({ "EXCEPTION": r#"{"ERROR":"ADAPTER_ERROR","MESSAGE":"There was an error"}"# }),
);
test_ok2!(
Value::Exception(AvialError {
error: Error::Network,
message: "Connection lost".into()
}),
json!({ "EXCEPTION" : r#" { "ERROR" : "NETWORK_ERROR" , "MESSAGE" : "Connection lost" } "# }),
json!({ "EXCEPTION": r#"{"ERROR":"NETWORK_ERROR","MESSAGE":"Connection lost"}"# }),
);
test_err!(json!({ "EXCEPTION": r#"{"ERROR":"IDONTEXIST_ERROR","MESSAGE":"Oh no"}"# }));
test_err!(json!({ "EXCEPTION": r#"{"ERR":"ADAPTER_ERROR","MESSAGE":"Oh no no o"}"# }));
test_err!(json!({ "EXCEPTION": r#"{"ERROR":"ADAPTER_ERROR","MSG":"Oh no no no "}"# }));
test_err!(json!({ "EXCEPTION": r#"{"MESSAGE":"Oh no no no no no no no no no no"}"# }));
test_err!(json!({ "EXCEPTION": r#"{"ERROR":"ADAPTER_ERROR"}"# }));
test_err!(json!({ "EXCEPTION": r#"{}"# }));
test_err!(json!({ "EXCEPTION": r#"[]"# }));
test_err!(json!({ "EXCEPTION": r#""# }));
}
#[test]
fn operator() {
test_ok!(
Value::Operator(Operator::Halt),
json!( { "OPERATOR": "HALT_OPERATOR" })
);
// We accept both form, but the standard is the one with the _OPERATOR suffix
test_ok2!(
Value::Operator(Operator::Return),
json!( { "OPERATOR": "RETURN" }),
json!( { "OPERATOR": "RETURN_OPERATOR" }),
);
test_err!(json!({ "OPERATOR": "" }));
test_err!(json!({ "OPERATOR": "YOUSHALLNOTPASS" }));
}
#[test]
fn function() {
test_ok!(
Value::Function(Entity {
pid: 0,
hid: 0,
uid: 24
}),
json!({ "FUNCTION": "<0|0|24>" })
);
test_ok!(
Value::Function(Entity {
pid: 0,
hid: 0,
uid: 24
}),
json!({ "FUNCTION": "<0|0|24>" })
);
test_err!(json!({ "FUNCTION": "" }));
test_err!(json!({ "FUNCTION": "<0|0|>" }));
test_err!(json!({ "FUNCTION": "<0|0|24" }));
test_err!(json!({ "FUNCTION": "<0|0|24|>" }));
test_err!(json!({ "FUNCTION": "<0|0|24|24>" }));
test_err!(json!({ "FUNCTION": "0|0|24>" }));
// This is not standard and shouldn't be allowed
// except locally, where which server is referred to is obvious from context
test_err!(json!({ "FUNCTION": "<24>" }));
}
#[test]
fn authorization() {
test_ok!(
Value::Authorization(Token::from_str("2d754e46-6259-45f6-ac46-745f42f02558").unwrap()),
json!({ "AUTHORIZATION": "2d754e46-6259-45f6-ac46-745f42f02558" }),
);
test_ok!(
Value::Authorization(Token::from_str("d1159412-e368-4694-9289-5ce02979c523").unwrap()),
json!({ "AUTHORIZATION": "d1159412-e368-4694-9289-5ce02979c523" }),
);
test_ok2!(
Value::Authorization(Token::NOAUTH),
json!({ "AUTHORIZATION": "********-****-****-****-************" }),
json!({ "AUTHORIZATION": "ffffffff-ffff-ffff-ffff-ffffffffffff" }),
);
test_ok!(
Value::Authorization(Token::NOAUTH),
json!({ "AUTHORIZATION": "ffffffff-ffff-ffff-ffff-ffffffffffff" }),
);
test_ok!(
Value::Authorization(Token::NULL),
json!({ "AUTHORIZATION": "00000000-0000-0000-0000-000000000000" }),
);
test_err!(json!({ "AUTHORIZATION": "" }));
test_err!(json!({ "AUTHORIZATION": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }));
test_err!(json!({ "AUTHORIZATION": "hello world" }));
test_err!(json!({ "AUTHORIZATION": "你好" }));
test_err!(json!({ "AUTHORIZATION": 0}));
test_err!(json!({ "AUTHORIZATION": {}}));
}
#[test]
fn date() {
test_ok!(
Value::Authorization(Token::from_str("2d754e46-6259-45f6-ac46-745f42f02558").unwrap()),
json!({ "AUTHORIZATION": "2d754e46-6259-45f6-ac46-745f42f02558" }),
);
test_ok!(
Value::Measurement(UnimplementedValue(
"{\"YEAR\": 1960,\"MONTH\": 10,\"DAY\": 1}".to_string()
)),
json!(
{
"MEASUREMENT": "{\"YEAR\": 1960,\"MONTH\": 10,\"DAY\": 1}"
}),
);
}
#[test]
fn aggregate() {
test_ok_nojsoncheck!(
Value::Aggregate(hashmap! {
"Boolean Variable".to_owned() => Value::Boolean(true),
"Integer Variable".to_owned() => Value::Integer(42),
}),
json!({
"AGGREGATE": "{\"Boolean Variable\":{\"BOOLEAN\":\"TRUE\"},\"Integer Variable\":{\"INTEGER\":\"42\"}}"
}),
);
test_ok!(
Value::Aggregate(hashmap! {}),
json!({
"AGGREGATE": "{}"
}),
);
test_ok!(
Value::Aggregate(hashmap! {
"Nested".to_owned() => Value::Aggregate(hashmap! {}),
}),
json!({
"AGGREGATE": "{\"Nested\":{\"AGGREGATE\":\"{}\"}}"
}),
);
test_err!(json!({ "AGGREGATE": "" }));
test_err!(json!({ "AGGREGATE": "}" }));
test_err!(json!({ "AGGREGATE": "32" }));
test_err!(json!({ "AGGREGATE": "[]" }));
test_err!(json!({ "AGGREGATE": "hi" }));
test_err!(json!({ "AGGREGATE": "\"MyValue\": {\"INTEGER\": \"string!\"}" }));
}
#[test]
fn array() {
test_ok!(
Value::Array(vec![
Value::Entity(Entity {
pid: 0,
hid: 0,
uid: 24,
}),
Value::Integer(42),
]),
json!({
"ARRAY": "[{\"ENTITY\":\"<0|0|24>\"},{\"INTEGER\":\"42\"}]"
}),
);
test_ok!(
Value::Array(vec![
Value::String(AsciiString::from_ascii("Hello").unwrap()),
Value::Float(7.2939),
Value::Array(vec![Value::Character(AsciiChar::A), Value::Boolean(true),]),
Value::Entity(Entity {
pid: 0,
hid: 0,
uid: 24
}),
]),
json!({
"ARRAY": "[{\"STRING\":\"Hello\"},{\"FLOAT\":\"7.2939\"},{\"ARRAY\":\"[{\\\"CHARACTER\\\":\\\"A\\\"},{\\\"BOOLEAN\\\":\\\"TRUE\\\"}]\"},{\"ENTITY\":\"<0|0|24>\"}]"
}),
);
test_ok!(
Value::Array(vec![
Value::String(AsciiString::from_ascii("Hello").unwrap()),
Value::Float(7.2939),
Value::Aggregate(hashmap! {
"Key".to_owned() => Value::Character(AsciiChar::A)
}),
Value::Entity(Entity {
pid: 0,
hid: 0,
uid: 24
}),
]),
json!({
"ARRAY": "[{\"STRING\":\"Hello\"},{\"FLOAT\":\"7.2939\"},{\"AGGREGATE\":\"{\\\"Key\\\":{\\\"CHARACTER\\\":\\\"A\\\"}}\"},{\"ENTITY\":\"<0|0|24>\"}]"
}),
);
test_ok!(
Value::Array(vec![
Value::String(AsciiString::from_ascii("Hello").unwrap()),
Value::Float(7.2939),
Value::Array(vec![Value::Character(AsciiChar::A), Value::Boolean(true),]),
]),
json!({
"ARRAY": "[{\"STRING\":\"Hello\"},{\"FLOAT\":\"7.2939\"},{\"ARRAY\":\"[{\\\"CHARACTER\\\":\\\"A\\\"},{\\\"BOOLEAN\\\":\\\"TRUE\\\"}]\"}]"
}),
);
test_ok!(
Value::Array(vec![]),
json!({
"ARRAY": "[]"
}),
);
test_err!(json!({ "ARRAY": "" }));
test_err!(json!({ "ARRAY": "}" }));
test_err!(json!({ "ARRAY": "32" }));
test_err!(json!({ "ARRAY": "{}" }));
test_err!(json!({ "ARRAY": "hi" }));
}
#[test]
fn variable() {
test_ok!(
Value::Variable(
"City Variable".to_owned(),
Box::new(Value::String(
AsciiString::from_ascii("Washington, D.C.").unwrap()
))
),
json!({
"VARIABLE": "{\"City Variable\":{\"STRING\":\"Washington, D.C.\"}}"
}),
);
test_ok!(
Value::Variable("MyVariable".to_owned(), Box::new(Value::Array(vec![]))),
json!({
"VARIABLE": "{\"MyVariable\":{\"ARRAY\":\"[]\"}}"
}),
);
test_ok_nojsoncheck!(
Value::Variable(
"MyVariable".to_owned(),
Box::new(Value::Aggregate(hashmap! {
"Thing 1".to_owned() => Value::Integer(21),
"Thing 2".to_owned() => Value::Boolean(true),
}))
),
json!({
"VARIABLE": "{\"MyVariable\":{\"AGGREGATE\":\"{\\\"Thing 1\\\":{\\\"INTEGER\\\":\\\"21\\\"},\\\"Thing 2\\\":{\\\"BOOLEAN\\\":\\\"TRUE\\\"}}\"}}"
}),
);
test_err!(json!({ "VARIABLE": "" }));
test_err!(json!({ "VARIABLE": "}" }));
test_err!(json!({ "VARIABLE": "32" }));
test_err!(json!({ "VARIABLE": "{}" }));
test_err!(json!({ "VARIABLE": "hi" }));
}
#[test]
fn locutor() {
test_ok_nojsoncheck!(
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,
offset: 0,
parameter: -1,
resultant: 0,
count: 123,
index: 1,
event: Event::Avesterra,
mode: Mode::Avesterra,
state: State::Null,
condition: Condition::Null,
precedence: 8,
time: OffsetDateTime::from_unix_timestamp(0).unwrap(),
timeout: 60,
aspect: Aspect::Avesterra,
template: Template::Null,
scheme: Scheme::Null,
name: String255::unchecked("Example Name"),
label: String::new(),
key: String255::NULL,
value: Value::String(AsciiString::from_str("Example String").unwrap()),
format: Format::Null,
authority: Token::from_str("********-****-****-****-************").unwrap(),
authorization: Token::NULL,
})),
json!(
{
"LOCUTOR": "{\"ENTITY\": \"<0|0|24>\",\"OUTLET\": \"<0|0|11>\",\"AUXILIARY\": \"<0|0|1>\",\"ANCILLARY\": \"<0|0|2>\",\"CONTEXT\": \"AVESTERRA_CONTEXT\",\"CATEGORY\": \"AVESTERRA_CATEGORY\",\"CLASS\": \"AVESTERRA_CLASS\",\"METHOD\": \"AVESTERRA_METHOD\",\"ATTRIBUTE\": \"AVESTERRA_ATTRIBUTE\",\"INSTANCE\": 1,\"NAME\": \"Example Name\",\"VALUE\": \"Example String\",\"VALUE_TAG\": \"STRING_TAG\",\"INDEX\": 1,\"COUNT\": 123,\"PRECEDENCE\": \" 8\",\"PARAMETER\": -1,\"MODE\": \"AVESTERRA_MODE\",\"EVENT\": \"AVESTERRA_EVENT\",\"TIMEOUT\": 60,\"ASPECT\": \"AVESTERRA_ASPECT\",\"AUTHORITY\": \"********-****-****-****-************\"}"
}),
);
test_err!(json!({ "LOCUTOR": "" }));
test_err!(json!({ "LOCUTOR": "}" }));
test_err!(json!({ "LOCUTOR": "32" }));
test_err!(json!({ "LOCUTOR": "hi" }));
}
#[test]
fn measurement() {
test_ok!(
Value::Measurement(UnimplementedValue("{\"FLOAT\": 4.20000000000000E+01,\"UNIT\": \"GRAM_UNIT\",\"PREFIX\": \"MICRO_PREFIX\",\"CONFIDENCE\": 9.99000015258789E+01,\"UNCERTAINTY\": 1.00000001490116E-01}".to_string())),
json!(
{
"MEASUREMENT": "{\"FLOAT\": 4.20000000000000E+01,\"UNIT\": \"GRAM_UNIT\",\"PREFIX\": \"MICRO_PREFIX\",\"CONFIDENCE\": 9.99000015258789E+01,\"UNCERTAINTY\": 1.00000001490116E-01}"
}),
);
}
}