use std::fmt;
use crate::graph::schema::ValueType;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum StreamKind {
Vertex,
Edge,
Path,
Graph,
}
impl fmt::Display for StreamKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
StreamKind::Vertex => f.write_str("vertex"),
StreamKind::Edge => f.write_str("edge"),
StreamKind::Path => f.write_str("path"),
StreamKind::Graph => f.write_str("graph"),
}
}
}
#[derive(Debug, Clone, PartialEq)]
#[non_exhaustive]
pub enum GType {
Value(ValueType),
Number,
List,
Map,
Stream(StreamKind),
}
impl GType {
pub const LONG: GType = GType::Value(ValueType::Int64);
pub const INT: GType = GType::Value(ValueType::Int64);
pub const DOUBLE: GType = GType::Value(ValueType::Float64);
pub const FLOAT: GType = GType::Value(ValueType::Float64);
pub const STRING: GType = GType::Value(ValueType::String);
pub const BOOLEAN: GType = GType::Value(ValueType::Boolean);
pub const NULL: GType = GType::Value(ValueType::Null);
pub const UUID: GType = GType::Value(ValueType::Uuid);
pub const NUMBER: GType = GType::Number;
pub const LIST: GType = GType::List;
pub const MAP: GType = GType::Map;
pub const VERTEX: GType = GType::Stream(StreamKind::Vertex);
pub const EDGE: GType = GType::Stream(StreamKind::Edge);
pub const PATH: GType = GType::Stream(StreamKind::Path);
pub const GRAPH: GType = GType::Stream(StreamKind::Graph);
pub(crate) fn token(&self) -> String {
let name = match self {
GType::Value(ValueType::Null) => "NULL",
GType::Value(ValueType::Boolean) => "BOOLEAN",
GType::Value(ValueType::Int64) => "LONG",
GType::Value(ValueType::Float64) => "DOUBLE",
GType::Value(ValueType::String) => "STRING",
GType::Value(ValueType::Uuid) => "UUID",
GType::Value(ValueType::Array(_)) => "LIST",
GType::Value(ValueType::Object) => "MAP",
GType::Value(union @ ValueType::Union(_)) => return union.to_string(),
GType::Number => "NUMBER",
GType::List => "LIST",
GType::Map => "MAP",
GType::Stream(StreamKind::Vertex) => "VERTEX",
GType::Stream(StreamKind::Edge) => "EDGE",
GType::Stream(StreamKind::Path) => "PATH",
GType::Stream(StreamKind::Graph) => "GRAPH",
};
format!("GType.{name}")
}
pub const TYPE_NAMES: &'static [&'static str] = &[
"String", "Integer", "Long", "Double", "Float", "Boolean", "UUID", "Number", "List", "Map",
"Vertex", "Edge", "Path", "Graph",
];
pub fn from_type_name(name: &str) -> Option<GType> {
Some(match name {
"String" => GType::STRING,
"Integer" | "Long" => GType::LONG,
"Double" | "Float" => GType::DOUBLE,
"Boolean" => GType::BOOLEAN,
"UUID" => GType::UUID,
"Number" => GType::NUMBER,
"List" => GType::LIST,
"Map" => GType::MAP,
"Vertex" => GType::VERTEX,
"Edge" => GType::EDGE,
"Path" => GType::PATH,
"Graph" => GType::GRAPH,
_ => return None,
})
}
pub fn matches(&self, observed: &ValueType) -> bool {
match self {
GType::Value(declared) => declared == observed,
GType::Number => matches!(observed, ValueType::Int64 | ValueType::Float64),
GType::List => matches!(observed, ValueType::Array(_)),
GType::Map => matches!(observed, ValueType::Object),
GType::Stream(_) => false,
}
}
pub fn as_type(&self) -> Option<&ValueType> {
match self {
GType::Value(declared) => Some(declared),
_ => None,
}
}
}
impl From<ValueType> for GType {
fn from(value: ValueType) -> Self {
GType::Value(value)
}
}
impl From<&ValueType> for GType {
fn from(value: &ValueType) -> Self {
GType::Value(value.clone())
}
}
impl From<StreamKind> for GType {
fn from(value: StreamKind) -> Self {
GType::Stream(value)
}
}
impl fmt::Display for GType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
GType::Value(declared) => write!(f, "{declared}"),
GType::Number => f.write_str("number"),
GType::List => f.write_str("list"),
GType::Map => f.write_str("map"),
GType::Stream(kind) => write!(f, "{kind}"),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn vanilla_constants_map_onto_the_type_vocabulary() {
assert_eq!(GType::LONG.as_type(), Some(&ValueType::Int64));
assert_eq!(GType::INT.as_type(), Some(&ValueType::Int64));
assert_eq!(GType::DOUBLE.as_type(), Some(&ValueType::Float64));
assert_eq!(GType::FLOAT.as_type(), Some(&ValueType::Float64));
assert_eq!(GType::STRING.as_type(), Some(&ValueType::String));
assert_eq!(GType::BOOLEAN.as_type(), Some(&ValueType::Boolean));
assert_eq!(GType::NULL.as_type(), Some(&ValueType::Null));
assert_eq!(GType::UUID.as_type(), Some(&ValueType::Uuid));
}
#[test]
fn family_tokens_match_more_than_one_type() {
assert!(GType::NUMBER.matches(&ValueType::Int64));
assert!(GType::NUMBER.matches(&ValueType::Float64));
assert!(!GType::NUMBER.matches(&ValueType::String));
assert!(GType::LIST.matches(&ValueType::Array(Box::new(ValueType::String))));
assert!(GType::LIST.matches(&ValueType::Array(Box::new(ValueType::Null))));
assert!(!GType::LIST.matches(&ValueType::String));
assert!(GType::MAP.matches(&ValueType::Object));
assert!(!GType::MAP.matches(&ValueType::Array(Box::new(ValueType::String))));
}
#[test]
fn value_tokens_match_exactly() {
assert!(GType::STRING.matches(&ValueType::String));
assert!(!GType::STRING.matches(&ValueType::Uuid));
assert!(!GType::LONG.matches(&ValueType::Float64));
}
#[test]
fn a_union_target_is_matched_literally() {
let union = ValueType::Union(vec![ValueType::Int64, ValueType::String]);
assert!(GType::from(union.clone()).matches(&union));
assert!(!GType::from(union).matches(&ValueType::Int64));
}
#[test]
fn element_tokens_never_match_a_value_type() {
assert!(!GType::VERTEX.matches(&ValueType::Object));
assert!(!GType::EDGE.matches(&ValueType::Object));
assert!(!GType::PATH.matches(&ValueType::Array(Box::new(ValueType::Null))));
}
#[test]
fn display_is_the_stable_rendering() {
assert_eq!(GType::LONG.to_string(), "int64");
assert_eq!(GType::NUMBER.to_string(), "number");
assert_eq!(GType::LIST.to_string(), "list");
assert_eq!(GType::MAP.to_string(), "map");
assert_eq!(GType::VERTEX.to_string(), "vertex");
}
#[test]
fn every_type_name_resolves_and_token_names_do_not() {
for name in GType::TYPE_NAMES {
assert!(GType::from_type_name(name).is_some(), "{name}");
}
assert_eq!(GType::from_type_name("Graph"), Some(GType::GRAPH));
assert_eq!(GType::from_type_name("LONG"), None);
assert_eq!(GType::from_type_name("GRAPH"), None);
}
#[test]
fn type_of_takes_tokens_and_names_alike() {
crate::test_dual!(
graph: crate::GraphSource::tinkerpop_modern(),
rust: |g| g
.e(None)
.has_label("knows")
.subgraph("sg")
.cap("sg")
.is(crate::P::type_of(GType::GRAPH))
.count()
.to_list(),
rhai: [
r#"g.E().hasLabel("knows").subgraph("sg").cap("sg").is(P.typeOf(GType.GRAPH)).count()"#,
r#"g.E().hasLabel("knows").subgraph("sg").cap("sg").is(P.typeOf("Graph")).count()"#,
r#"g.e().has_label("knows").subgraph("sg").cap("sg").is(P::typeOf(GType::GRAPH)).count()"#,
r#"g.e().has_label("knows").subgraph("sg").cap("sg").is(typeOf("Graph")).count()"#,
],
assert: |res| assert_eq!(res, &[1.into()]),
);
crate::test_dual!(
graph: crate::GraphSource::tinkerpop_modern(),
rust: |g| g.v(None).values("age").is(crate::P::type_of(GType::LONG)).count().to_list(),
rhai: [
r#"g.V().values("age").is(P.typeOf(GType.LONG)).count()"#,
r#"g.V().values("age").is(P.typeOf("Long")).count()"#,
r#"g.v().values("age").is(P::typeOf(GType::INT)).count()"#,
r#"g.v().values("age").is(typeOf("Integer")).count()"#,
],
assert: |res| assert_eq!(res, &[4.into()]),
);
}
}