typedb_driver/answer/
concept_document.rs

1/*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements.  See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership.  The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License.  You may obtain a copy of the License at
9 *
10 *   http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied.  See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19
20use 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/// A single document of concepts representing substitutions for variables in the query.
33/// Contains a Header (query type), and the document of concepts.
34#[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    /// Retrieve the executed query's type (shared by all elements in this stream).
53    ///
54    /// # Examples
55    ///
56    /// ```rust
57    /// concept_document.get_query_type()
58    /// ```
59    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}