use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;
use std::collections::HashMap;
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[serde(untagged)]
pub enum GraphSONValue {
Typed {
#[serde(rename = "@type")]
type_tag: String,
#[serde(rename = "@value")]
value: Box<JsonValue>,
},
Untyped(JsonValue),
}
impl GraphSONValue {
pub fn int64(n: i64) -> Self {
GraphSONValue::Typed {
type_tag: "g:Int64".to_string(),
value: Box::new(JsonValue::Number(n.into())),
}
}
pub fn int32(n: i32) -> Self {
GraphSONValue::Typed {
type_tag: "g:Int32".to_string(),
value: Box::new(JsonValue::Number(n.into())),
}
}
pub fn double(f: f64) -> Self {
GraphSONValue::Typed {
type_tag: "g:Double".to_string(),
value: Box::new(
serde_json::Number::from_f64(f)
.map(JsonValue::Number)
.unwrap_or(JsonValue::Null),
),
}
}
pub fn float(f: f32) -> Self {
GraphSONValue::Typed {
type_tag: "g:Float".to_string(),
value: Box::new(
serde_json::Number::from_f64(f as f64)
.map(JsonValue::Number)
.unwrap_or(JsonValue::Null),
),
}
}
pub fn list(items: Vec<GraphSONValue>) -> Self {
GraphSONValue::Typed {
type_tag: "g:List".to_string(),
value: Box::new(JsonValue::Array(
items
.into_iter()
.map(|v| serde_json::to_value(v).unwrap_or(JsonValue::Null))
.collect(),
)),
}
}
pub fn set(items: Vec<GraphSONValue>) -> Self {
GraphSONValue::Typed {
type_tag: "g:Set".to_string(),
value: Box::new(JsonValue::Array(
items
.into_iter()
.map(|v| serde_json::to_value(v).unwrap_or(JsonValue::Null))
.collect(),
)),
}
}
pub fn map(pairs: Vec<(GraphSONValue, GraphSONValue)>) -> Self {
let flattened: Vec<JsonValue> = pairs
.into_iter()
.flat_map(|(k, v)| {
vec![
serde_json::to_value(k).unwrap_or(JsonValue::Null),
serde_json::to_value(v).unwrap_or(JsonValue::Null),
]
})
.collect();
GraphSONValue::Typed {
type_tag: "g:Map".to_string(),
value: Box::new(JsonValue::Array(flattened)),
}
}
pub fn string(s: impl Into<String>) -> Self {
GraphSONValue::Untyped(JsonValue::String(s.into()))
}
pub fn boolean(b: bool) -> Self {
GraphSONValue::Untyped(JsonValue::Bool(b))
}
pub fn null() -> Self {
GraphSONValue::Untyped(JsonValue::Null)
}
pub fn uuid(s: impl Into<String>) -> Self {
GraphSONValue::Typed {
type_tag: "g:UUID".to_string(),
value: Box::new(JsonValue::String(s.into())),
}
}
pub fn date(ms: i64) -> Self {
GraphSONValue::Typed {
type_tag: "g:Date".to_string(),
value: Box::new(JsonValue::Number(ms.into())),
}
}
pub fn type_tag(&self) -> Option<&str> {
match self {
GraphSONValue::Typed { type_tag, .. } => Some(type_tag),
GraphSONValue::Untyped(_) => None,
}
}
pub fn inner_value(&self) -> &JsonValue {
match self {
GraphSONValue::Typed { value, .. } => value,
GraphSONValue::Untyped(v) => v,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphSONVertexProperty {
pub id: GraphSONValue,
pub label: String,
pub value: GraphSONValue,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub properties: HashMap<String, GraphSONValue>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphSONVertex {
pub id: GraphSONValue,
pub label: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub properties: HashMap<String, Vec<TypedVertexProperty>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypedVertexProperty {
#[serde(rename = "@type")]
pub type_tag: String,
#[serde(rename = "@value")]
pub value: GraphSONVertexProperty,
}
impl TypedVertexProperty {
pub fn new(prop: GraphSONVertexProperty) -> Self {
TypedVertexProperty {
type_tag: "g:VertexProperty".to_string(),
value: prop,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphSONProperty {
pub key: String,
pub value: GraphSONValue,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypedProperty {
#[serde(rename = "@type")]
pub type_tag: String,
#[serde(rename = "@value")]
pub value: GraphSONProperty,
}
impl TypedProperty {
pub fn new(prop: GraphSONProperty) -> Self {
TypedProperty {
type_tag: "g:Property".to_string(),
value: prop,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphSONEdge {
pub id: GraphSONValue,
pub label: String,
#[serde(rename = "outV")]
pub out_v: GraphSONValue,
#[serde(rename = "outVLabel")]
pub out_v_label: String,
#[serde(rename = "inV")]
pub in_v: GraphSONValue,
#[serde(rename = "inVLabel")]
pub in_v_label: String,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub properties: HashMap<String, TypedProperty>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct GraphSONGraph {
#[serde(default)]
pub vertices: Vec<TypedVertex>,
#[serde(default)]
pub edges: Vec<TypedEdge>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypedVertex {
#[serde(rename = "@type")]
pub type_tag: String,
#[serde(rename = "@value")]
pub value: GraphSONVertex,
}
impl TypedVertex {
pub fn new(vertex: GraphSONVertex) -> Self {
TypedVertex {
type_tag: "g:Vertex".to_string(),
value: vertex,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypedEdge {
#[serde(rename = "@type")]
pub type_tag: String,
#[serde(rename = "@value")]
pub value: GraphSONEdge,
}
impl TypedEdge {
pub fn new(edge: GraphSONEdge) -> Self {
TypedEdge {
type_tag: "g:Edge".to_string(),
value: edge,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TypedGraph {
#[serde(rename = "@type")]
pub type_tag: String,
#[serde(rename = "@value")]
pub value: GraphSONGraph,
}
impl TypedGraph {
pub fn new(graph: GraphSONGraph) -> Self {
TypedGraph {
type_tag: "tinker:graph".to_string(),
value: graph,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_int64_creation() {
let v = GraphSONValue::int64(42);
assert_eq!(v.type_tag(), Some("g:Int64"));
let json = serde_json::to_string(&v).unwrap();
assert_eq!(json, r#"{"@type":"g:Int64","@value":42}"#);
}
#[test]
fn test_double_creation() {
let v = GraphSONValue::double(3.14);
assert_eq!(v.type_tag(), Some("g:Double"));
let json = serde_json::to_string(&v).unwrap();
assert_eq!(json, r#"{"@type":"g:Double","@value":3.14}"#);
}
#[test]
fn test_string_creation() {
let v = GraphSONValue::string("hello");
assert_eq!(v.type_tag(), None);
let json = serde_json::to_string(&v).unwrap();
assert_eq!(json, r#""hello""#);
}
#[test]
fn test_boolean_creation() {
let v = GraphSONValue::boolean(true);
assert_eq!(v.type_tag(), None);
let json = serde_json::to_string(&v).unwrap();
assert_eq!(json, "true");
}
#[test]
fn test_null_creation() {
let v = GraphSONValue::null();
assert_eq!(v.type_tag(), None);
let json = serde_json::to_string(&v).unwrap();
assert_eq!(json, "null");
}
#[test]
fn test_list_creation() {
let v = GraphSONValue::list(vec![
GraphSONValue::int64(1),
GraphSONValue::int64(2),
GraphSONValue::int64(3),
]);
assert_eq!(v.type_tag(), Some("g:List"));
let json = serde_json::to_string(&v).unwrap();
assert!(json.contains("g:List"));
}
#[test]
fn test_map_creation() {
let v = GraphSONValue::map(vec![
(GraphSONValue::string("key1"), GraphSONValue::int64(1)),
(GraphSONValue::string("key2"), GraphSONValue::int64(2)),
]);
assert_eq!(v.type_tag(), Some("g:Map"));
let json = serde_json::to_string(&v).unwrap();
assert!(json.contains("g:Map"));
}
#[test]
fn test_typed_vertex_serialization() {
let vertex = TypedVertex::new(GraphSONVertex {
id: GraphSONValue::int64(1),
label: "person".to_string(),
properties: HashMap::new(),
});
let json = serde_json::to_string(&vertex).unwrap();
assert!(json.contains("g:Vertex"));
assert!(json.contains("person"));
}
#[test]
fn test_typed_edge_serialization() {
let edge = TypedEdge::new(GraphSONEdge {
id: GraphSONValue::int64(100),
label: "knows".to_string(),
out_v: GraphSONValue::int64(1),
out_v_label: "person".to_string(),
in_v: GraphSONValue::int64(2),
in_v_label: "person".to_string(),
properties: HashMap::new(),
});
let json = serde_json::to_string(&edge).unwrap();
assert!(json.contains("g:Edge"));
assert!(json.contains("knows"));
assert!(json.contains("outV"));
assert!(json.contains("inV"));
}
#[test]
fn test_typed_graph_serialization() {
let graph = TypedGraph::new(GraphSONGraph {
vertices: vec![],
edges: vec![],
});
let json = serde_json::to_string(&graph).unwrap();
assert!(json.contains("tinker:graph"));
}
#[test]
fn test_deserialize_typed_value() {
let json = r#"{"@type":"g:Int64","@value":42}"#;
let v: GraphSONValue = serde_json::from_str(json).unwrap();
assert_eq!(v.type_tag(), Some("g:Int64"));
}
#[test]
fn test_deserialize_untyped_value() {
let json = r#""hello""#;
let v: GraphSONValue = serde_json::from_str(json).unwrap();
assert_eq!(v.type_tag(), None);
assert_eq!(v.inner_value(), &JsonValue::String("hello".to_string()));
}
}