use std::{borrow::Cow, collections::HashMap, sync::Arc};
use super::{QueryType, JSON};
use crate::concept::{
value::Struct, Attribute, AttributeType, Concept, EntityType, Kind, RelationType, RoleType, Value, ValueType,
};
#[derive(Debug, PartialEq)]
pub struct ConceptDocumentHeader {
pub query_type: QueryType,
}
#[derive(Debug, Clone, PartialEq)]
pub struct ConceptDocument {
header: Arc<ConceptDocumentHeader>,
pub root: Option<Node>,
}
impl ConceptDocument {
pub fn new(header: Arc<ConceptDocumentHeader>, root: Option<Node>) -> Self {
Self { header, root }
}
pub fn into_json(self) -> JSON {
match self.root {
None => JSON::Null,
Some(root_node) => root_node.into_json(),
}
}
pub fn get_query_type(&self) -> QueryType {
self.header.query_type
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Node {
Map(HashMap<String, Node>),
List(Vec<Node>),
Leaf(Option<Leaf>),
}
impl Node {
pub(crate) fn into_json(self) -> JSON {
match self {
Node::Map(map) => {
JSON::Object(map.into_iter().map(|(var, node)| (Cow::Owned(var), node.into_json())).collect())
}
Node::List(list) => JSON::Array(list.into_iter().map(Node::into_json).collect()),
Node::Leaf(Some(leaf)) => leaf.into_json(),
Node::Leaf(None) => JSON::Null,
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub enum Leaf {
Empty,
Concept(Concept),
ValueType(ValueType),
Kind(Kind),
}
impl Leaf {
fn into_json(self) -> JSON {
match self {
Self::Empty => JSON::Null,
Self::Concept(Concept::EntityType(EntityType { label, .. })) => json_type(Kind::Entity, Cow::Owned(label)),
Self::Concept(Concept::RelationType(RelationType { label, .. })) => {
json_type(Kind::Relation, Cow::Owned(label))
}
Self::Concept(Concept::AttributeType(AttributeType { label, value_type, .. })) => {
json_attribute_type(Cow::Owned(label), value_type)
}
Self::Concept(Concept::RoleType(RoleType { label, .. })) => {
json_type(Kind::Role, Cow::Owned(label.to_string()))
}
Self::Concept(Concept::Attribute(Attribute { value, .. })) => json_value(value),
Self::Concept(Concept::Value(value)) => json_value(value),
Self::Concept(concept @ (Concept::Entity(_) | Concept::Relation(_))) => {
unreachable!("Unexpected concept encountered in fetch response: {:?}", concept)
}
Self::ValueType(value_type) => json_value_type(Some(value_type)),
Self::Kind(kind) => json_kind(kind),
}
}
}
const KIND: Cow<'static, str> = Cow::Borrowed("kind");
const LABEL: Cow<'static, str> = Cow::Borrowed("label");
const VALUE_TYPE: Cow<'static, str> = Cow::Borrowed("valueType");
fn json_type(kind: Kind, label: Cow<'static, str>) -> JSON {
JSON::Object([(KIND, json_kind(kind)), (LABEL, JSON::String(label))].into())
}
fn json_attribute_type(label: Cow<'static, str>, value_type: Option<ValueType>) -> JSON {
JSON::Object(
[
(KIND, JSON::String(Cow::Borrowed(Kind::Attribute.name()))),
(LABEL, JSON::String(label)),
(VALUE_TYPE, json_value_type(value_type)),
]
.into(),
)
}
fn json_value_type(value_type: Option<ValueType>) -> JSON {
const NONE: Cow<'static, str> = Cow::Borrowed(ValueType::NONE_STR);
const BOOLEAN: Cow<'static, str> = Cow::Borrowed(ValueType::BOOLEAN_STR);
const INTEGER: Cow<'static, str> = Cow::Borrowed(ValueType::INTEGER_STR);
const DOUBLE: Cow<'static, str> = Cow::Borrowed(ValueType::DOUBLE_STR);
const DECIMAL: Cow<'static, str> = Cow::Borrowed(ValueType::DECIMAL_STR);
const STRING: Cow<'static, str> = Cow::Borrowed(ValueType::STRING_STR);
const DATE: Cow<'static, str> = Cow::Borrowed(ValueType::DATE_STR);
const DATETIME: Cow<'static, str> = Cow::Borrowed(ValueType::DATETIME_STR);
const DATETIME_TZ: Cow<'static, str> = Cow::Borrowed(ValueType::DATETIME_TZ_STR);
const DURATION: Cow<'static, str> = Cow::Borrowed(ValueType::DURATION_STR);
JSON::String(match value_type {
None => NONE,
Some(ValueType::Boolean) => BOOLEAN,
Some(ValueType::Integer) => INTEGER,
Some(ValueType::Double) => DOUBLE,
Some(ValueType::Decimal) => DECIMAL,
Some(ValueType::String) => STRING,
Some(ValueType::Date) => DATE,
Some(ValueType::Datetime) => DATETIME,
Some(ValueType::DatetimeTZ) => DATETIME_TZ,
Some(ValueType::Duration) => DURATION,
Some(ValueType::Struct(name)) => Cow::Owned(name),
})
}
fn json_value(value: Value) -> JSON {
match value {
Value::Boolean(bool) => JSON::Boolean(bool),
Value::Integer(integer) => JSON::Number(integer as f64),
Value::Double(double) => JSON::Number(double),
Value::String(string) => JSON::String(Cow::Owned(string)),
Value::Decimal(_) | Value::Date(_) | Value::Datetime(_) | Value::DatetimeTZ(_) | Value::Duration(_) => {
JSON::String(Cow::Owned(value.to_string()))
}
Value::Struct(struct_, struct_name) => {
JSON::Object(HashMap::from([(Cow::Owned(struct_name), json_struct(struct_))]))
}
}
}
fn json_struct(struct_: Struct) -> JSON {
let mut json_object = HashMap::new();
for (key, value_option) in struct_.fields {
let json_value = match value_option {
Some(value) => json_value(value),
None => JSON::Null,
};
json_object.insert(Cow::Owned(key), json_value);
}
JSON::Object(json_object)
}
fn json_kind(kind: Kind) -> JSON {
JSON::String(Cow::Borrowed(kind.name()))
}