1use std::{borrow::Cow, collections::HashMap, sync::Arc};
21
22use super::{QueryType, JSON};
23use crate::concept::{
24 value::Struct, Attribute, AttributeType, Concept, EntityType, Kind, RelationType, RoleType, Value, ValueType,
25};
26
27#[derive(Debug, PartialEq)]
28pub struct ConceptDocumentHeader {
29 pub query_type: QueryType,
30}
31
32#[derive(Debug, Clone, PartialEq)]
35pub struct ConceptDocument {
36 header: Arc<ConceptDocumentHeader>,
37 pub root: Option<Node>,
38}
39
40impl ConceptDocument {
41 pub fn new(header: Arc<ConceptDocumentHeader>, root: Option<Node>) -> Self {
42 Self { header, root }
43 }
44
45 pub fn into_json(self) -> JSON {
46 match self.root {
47 None => JSON::Null,
48 Some(root_node) => root_node.into_json(),
49 }
50 }
51
52 pub fn get_query_type(&self) -> QueryType {
60 self.header.query_type
61 }
62}
63
64#[derive(Clone, Debug, PartialEq)]
65pub enum Node {
66 Map(HashMap<String, Node>),
67 List(Vec<Node>),
68 Leaf(Option<Leaf>),
69}
70
71impl Node {
72 pub(crate) fn into_json(self) -> JSON {
73 match self {
74 Node::Map(map) => {
75 JSON::Object(map.into_iter().map(|(var, node)| (Cow::Owned(var), node.into_json())).collect())
76 }
77 Node::List(list) => JSON::Array(list.into_iter().map(Node::into_json).collect()),
78 Node::Leaf(Some(leaf)) => leaf.into_json(),
79 Node::Leaf(None) => JSON::Null,
80 }
81 }
82}
83
84#[derive(Clone, Debug, PartialEq)]
85pub enum Leaf {
86 Empty,
87 Concept(Concept),
88 ValueType(ValueType),
89 Kind(Kind),
90}
91
92impl Leaf {
93 fn into_json(self) -> JSON {
94 match self {
95 Self::Empty => JSON::Null,
96 Self::Concept(Concept::EntityType(EntityType { label, .. })) => json_type(Kind::Entity, Cow::Owned(label)),
97 Self::Concept(Concept::RelationType(RelationType { label, .. })) => {
98 json_type(Kind::Relation, Cow::Owned(label))
99 }
100 Self::Concept(Concept::AttributeType(AttributeType { label, value_type, .. })) => {
101 json_attribute_type(Cow::Owned(label), value_type)
102 }
103 Self::Concept(Concept::RoleType(RoleType { label, .. })) => {
104 json_type(Kind::Role, Cow::Owned(label.to_string()))
105 }
106 Self::Concept(Concept::Attribute(Attribute { value, .. })) => json_value(value),
107 Self::Concept(Concept::Value(value)) => json_value(value),
108 Self::Concept(concept @ (Concept::Entity(_) | Concept::Relation(_))) => {
109 unreachable!("Unexpected concept encountered in fetch response: {:?}", concept)
110 }
111 Self::ValueType(value_type) => json_value_type(Some(value_type)),
112 Self::Kind(kind) => json_kind(kind),
113 }
114 }
115}
116
117const KIND: Cow<'static, str> = Cow::Borrowed("kind");
118const LABEL: Cow<'static, str> = Cow::Borrowed("label");
119const VALUE_TYPE: Cow<'static, str> = Cow::Borrowed("value_type");
120
121fn json_type(kind: Kind, label: Cow<'static, str>) -> JSON {
122 JSON::Object([(KIND, json_kind(kind)), (LABEL, JSON::String(label))].into())
123}
124
125fn json_attribute_type(label: Cow<'static, str>, value_type: Option<ValueType>) -> JSON {
126 JSON::Object(
127 [
128 (KIND, JSON::String(Cow::Borrowed(Kind::Attribute.name()))),
129 (LABEL, JSON::String(label)),
130 (VALUE_TYPE, json_value_type(value_type)),
131 ]
132 .into(),
133 )
134}
135
136fn json_value_type(value_type: Option<ValueType>) -> JSON {
137 const NONE: Cow<'static, str> = Cow::Borrowed(ValueType::NONE_STR);
138 const BOOLEAN: Cow<'static, str> = Cow::Borrowed(ValueType::BOOLEAN_STR);
139 const INTEGER: Cow<'static, str> = Cow::Borrowed(ValueType::INTEGER_STR);
140 const DOUBLE: Cow<'static, str> = Cow::Borrowed(ValueType::DOUBLE_STR);
141 const DECIMAL: Cow<'static, str> = Cow::Borrowed(ValueType::DECIMAL_STR);
142 const STRING: Cow<'static, str> = Cow::Borrowed(ValueType::STRING_STR);
143 const DATE: Cow<'static, str> = Cow::Borrowed(ValueType::DATE_STR);
144 const DATETIME: Cow<'static, str> = Cow::Borrowed(ValueType::DATETIME_STR);
145 const DATETIME_TZ: Cow<'static, str> = Cow::Borrowed(ValueType::DATETIME_TZ_STR);
146 const DURATION: Cow<'static, str> = Cow::Borrowed(ValueType::DURATION_STR);
147
148 JSON::String(match value_type {
149 None => NONE,
150 Some(ValueType::Boolean) => BOOLEAN,
151 Some(ValueType::Integer) => INTEGER,
152 Some(ValueType::Double) => DOUBLE,
153 Some(ValueType::Decimal) => DECIMAL,
154 Some(ValueType::String) => STRING,
155 Some(ValueType::Date) => DATE,
156 Some(ValueType::Datetime) => DATETIME,
157 Some(ValueType::DatetimeTZ) => DATETIME_TZ,
158 Some(ValueType::Duration) => DURATION,
159 Some(ValueType::Struct(name)) => Cow::Owned(name),
160 })
161}
162
163fn json_value(value: Value) -> JSON {
164 match value {
165 Value::Boolean(bool) => JSON::Boolean(bool),
166 Value::Integer(integer) => JSON::Number(integer as f64),
167 Value::Double(double) => JSON::Number(double),
168 Value::String(string) => JSON::String(Cow::Owned(string)),
169
170 Value::Decimal(_) | Value::Date(_) | Value::Datetime(_) | Value::DatetimeTZ(_) | Value::Duration(_) => {
171 JSON::String(Cow::Owned(value.to_string()))
172 }
173
174 Value::Struct(struct_, struct_name) => {
175 JSON::Object(HashMap::from([(Cow::Owned(struct_name), json_struct(struct_))]))
176 }
177 }
178}
179
180fn json_struct(struct_: Struct) -> JSON {
181 let mut json_object = HashMap::new();
182
183 for (key, value_option) in struct_.fields {
184 let json_value = match value_option {
185 Some(value) => json_value(value),
186 None => JSON::Null,
187 };
188 json_object.insert(Cow::Owned(key), json_value);
189 }
190
191 JSON::Object(json_object)
192}
193
194fn json_kind(kind: Kind) -> JSON {
195 JSON::String(Cow::Borrowed(kind.name()))
196}