diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs index a0c1e50..45c0720 100644 --- a/gremlin-client/src/io/graph_binary_v1.rs +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -8,7 +8,7 @@ use crate::{ io::graph_binary_v1, message::{ReponseStatus, Response, ResponseResult}, process::traversal::{Instruction, Order, Scope}, - structure::{Column, Direction, Pop, Set, TextP, Traverser, P, T}, + structure::{Column, Direction, Merge, Pop, TextP, Traverser, T}, Cardinality, Edge, GKey, GValue, GremlinError, GremlinResult, Path, ToGValue, Vertex, VertexProperty, GID, }; @@ -34,7 +34,7 @@ const SET: u8 = 0x0B; const UUID: u8 = 0x0C; const EDGE: u8 = 0x0D; const PATH: u8 = 0x0E; -const PROPERTY: u8 = 0x0F; +// const PROPERTY: u8 = 0x0F; // const TINKERGRAPH: u8 = 0x10; const VERTEX: u8 = 0x11; const VERTEX_PROPERTY: u8 = 0x12; @@ -60,6 +60,7 @@ const TRAVERSER: u8 = 0x21; const BOOLEAN: u8 = 0x27; const TEXTP: u8 = 0x28; //... +const MERGE: u8 = 0x2E; const UNSPECIFIED_NULL_OBEJECT: u8 = 0xFE; pub(crate) struct RequestMessage<'a, 'b> { @@ -288,9 +289,6 @@ impl GraphBinaryV1Ser for &GValue { buf.push(VALUE_FLAG); value.to_be_bytes(buf)?; } - GValue::Property(property) => { - unimplemented!("") - } GValue::Vertex(vertex) => { buf.push(VERTEX); buf.push(VALUE_FLAG); @@ -298,9 +296,6 @@ impl GraphBinaryV1Ser for &GValue { vertex.label().to_be_bytes(buf)?; GValue::Null.to_be_bytes(buf)?; } - GValue::VertexProperty(vertex_property) => { - todo!() - } GValue::Bytecode(code) => { //Type code of 0x15: Bytecode buf.push(BYTECODE); @@ -386,13 +381,18 @@ impl GraphBinaryV1Ser for &GValue { buf.push(VALUE_FLAG); text_p.to_be_bytes(buf)?; } + GValue::Merge(merge) => { + buf.push(MERGE); + buf.push(VALUE_FLAG); + merge.to_be_bytes(buf)?; + } GValue::Null => { //Type code of 0xfe: Unspecified null object buf.push(UNSPECIFIED_NULL_OBEJECT); //Then the null {value_flag} set and no sequence of bytes. buf.push(VALUE_NULL_FLAG); } - other => unimplemented!("TODO {other:?}"), + other => unimplemented!("Serializing GValue {other:?}"), } Ok(()) } @@ -454,80 +454,106 @@ fn write_fully_qualified_str(value: &str, buf: &mut Vec) -> GremlinResult<() value.to_be_bytes(buf) } +impl GraphBinaryV1Ser for &Merge { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + let literal = match self { + Merge::OnCreate => "onCreate", + Merge::OnMatch => "onMatch", + Merge::OutV => "outV", + Merge::InV => "inV", + }; + write_fully_qualified_str(literal, buf) + } +} + impl GraphBinaryV1Ser for &Scope { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { //Format: a fully qualified single String representing the enum value. - match self { - Scope::Global => write_fully_qualified_str("global", buf), - Scope::Local => write_fully_qualified_str("local", buf), - } + let literal = match self { + Scope::Global => "global", + Scope::Local => "local", + }; + write_fully_qualified_str(literal, buf) } } impl GraphBinaryV1Ser for &Cardinality { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { - match self { - Cardinality::List => write_fully_qualified_str("list", buf), - Cardinality::Set => write_fully_qualified_str("set", buf), - Cardinality::Single => write_fully_qualified_str("single", buf), + let literal = match self { + Cardinality::List => "list", + Cardinality::Set => "set", + Cardinality::Single => "single", + }; + write_fully_qualified_str(literal, buf) + } +} + +impl GraphBinaryV1Deser for Direction { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + match GValue::from_be_bytes(bytes)? { + GValue::String(literal) if literal.eq_ignore_ascii_case("out") => Ok(Direction::Out), + GValue::String(literal) if literal.eq_ignore_ascii_case("in") => Ok(Direction::In), + other => Err(GremlinError::Cast(format!( + "Unexpected direction literal {other:?}" + ))), } } } impl GraphBinaryV1Ser for &Direction { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { - match self { - Direction::Out => write_fully_qualified_str("out", buf), - Direction::In => write_fully_qualified_str("in", buf), - Direction::From => write_fully_qualified_str("from", buf), - Direction::To => write_fully_qualified_str("to", buf), - } + let literal = match self { + Direction::Out | Direction::From => "OUT", + Direction::In | Direction::To => "IN", + }; + write_fully_qualified_str(literal, buf) } } impl GraphBinaryV1Ser for &Order { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { - match self { - Order::Asc => write_fully_qualified_str("asc", buf), - Order::Desc => write_fully_qualified_str("desc", buf), - Order::Shuffle => write_fully_qualified_str("shuffle", buf), - } + let literal = match self { + Order::Asc => "asc", + Order::Desc => "desc", + Order::Shuffle => "shuffle", + }; + write_fully_qualified_str(literal, buf) } } impl GraphBinaryV1Ser for &Pop { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { //Format: a fully qualified single String representing the enum value. - match self { - Pop::All => write_fully_qualified_str("all", buf), - Pop::First => write_fully_qualified_str("first", buf), - Pop::Last => write_fully_qualified_str("last", buf), - Pop::Mixed => write_fully_qualified_str("mixed", buf), - } + let literal = match self { + Pop::All => "all", + Pop::First => "first", + Pop::Last => "last", + Pop::Mixed => "mixed", + }; + write_fully_qualified_str(literal, buf) } } impl GraphBinaryV1Ser for &Column { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { //Format: a fully qualified single String representing the enum value. - match self { - Column::Keys => write_fully_qualified_str("keys", buf), - Column::Values => write_fully_qualified_str("values", buf), - } + let literal = match self { + Column::Keys => "keys", + Column::Values => "values", + }; + write_fully_qualified_str(literal, buf) } } impl GraphBinaryV1Ser for &GKey { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { match self { - GKey::T(t) => todo!(), - GKey::String(str) => (&GValue::from(str.clone())).to_be_bytes(buf), - GKey::Token(token) => todo!(), - GKey::Vertex(vertex) => todo!(), - GKey::Edge(edge) => todo!(), - GKey::Direction(direction) => todo!(), - GKey::Int64(_) => todo!(), - GKey::Int32(_) => todo!(), + GKey::T(t) => GValue::T(t.clone()).to_be_bytes(buf), + GKey::String(str) => write_fully_qualified_str(str, buf), + GKey::Direction(direction) => GValue::Direction(direction.clone()).to_be_bytes(buf), + GKey::Int64(i) => GValue::Int64(*i).to_be_bytes(buf), + GKey::Int32(i) => GValue::Int32(*i).to_be_bytes(buf), + other => unimplemented!("Unimplemented GKey serialization requested {other:?}"), } } } @@ -649,9 +675,6 @@ impl GraphBinaryV1Deser for GValue { Some(value) => GValue::Path(value), None => GValue::Null, }), - PROPERTY => { - todo!() - } VERTEX => Ok(match Vertex::from_be_bytes_nullable(bytes)? { Some(value) => GValue::Vertex(value), None => GValue::Null, @@ -660,6 +683,10 @@ impl GraphBinaryV1Deser for GValue { Some(value) => GValue::VertexProperty(value), None => GValue::Null, }), + DIRECTION => Ok(match Direction::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Direction(value), + None => GValue::Null, + }), T => Ok(match T::from_be_bytes_nullable(bytes)? { Some(value) => GValue::T(value), None => GValue::Null, @@ -705,12 +732,13 @@ impl GraphBinaryV1Deser for T { impl GraphBinaryV1Ser for &T { fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { - match self { - T::Id => write_fully_qualified_str("id", buf), - T::Key => write_fully_qualified_str("key", buf), - T::Label => write_fully_qualified_str("label", buf), - T::Value => write_fully_qualified_str("value", buf), - } + let literal = match self { + T::Id => "id", + T::Key => "key", + T::Label => "label", + T::Value => "value", + }; + write_fully_qualified_str(literal, buf) } } diff --git a/gremlin-client/tests/common.rs b/gremlin-client/tests/common.rs index 01eaa60..099a2c7 100644 --- a/gremlin-client/tests/common.rs +++ b/gremlin-client/tests/common.rs @@ -1,8 +1,14 @@ -use gremlin_client::Map; +use gremlin_client::{structure::T, Map}; pub fn assert_map_property(element_map: &Map, expected_key: &str, expected_value: &str) { let actual_prop_value: &String = element_map .get(expected_key) + .or(match expected_key { + "id" => element_map.get(T::Id), + "key" => element_map.get(T::Key), + "label" => element_map.get(T::Label), + _ => None, + }) .unwrap_or_else(|| panic!("Didn't have expected key {}", expected_key)) .get() .expect("Should be String"); diff --git a/gremlin-client/tests/integration_traversal_omni.rs b/gremlin-client/tests/integration_traversal_omni.rs index be9d93e..f3bf4fe 100644 --- a/gremlin-client/tests/integration_traversal_omni.rs +++ b/gremlin-client/tests/integration_traversal_omni.rs @@ -24,6 +24,14 @@ use common::io::{ graph_serializer, }; +//GraphSONV2 doesn't appear to support merge steps, so ommit it from +//being one of the serializers tested for those tests +#[template] +#[rstest] +#[case::graphson_v3(graph_serializer(IoProtocol::GraphSONV3))] +#[case::graph_binary_v1(graph_serializer(IoProtocol::GraphBinaryV1))] +fn merge_capable_serializers(#[case] client: GremlinClient) {} + #[template] #[rstest] #[case::graphson_v2(graph_serializer(IoProtocol::GraphSONV2))] @@ -41,7 +49,7 @@ mod merge_tests { }; use std::collections::HashMap; - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_v_no_options)] fn test_merge_v_no_options(client: GremlinClient) { let test_vertex_label = "test_merge_v_no_options"; @@ -76,7 +84,7 @@ mod merge_tests { assert_map_property(&vertex_properties, "propertyKey", "propertyValue"); } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_v_options)] fn test_merge_v_options(client: GremlinClient) { let expected_label = "test_merge_v_options"; @@ -127,7 +135,7 @@ mod merge_tests { assert_map_property(&on_match_vertex_map, prop_key, expected_on_match_prop_value); } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_v_start_step)] fn test_merge_v_start_step(client: GremlinClient) { let expected_label = "test_merge_v_start_step"; @@ -144,7 +152,7 @@ mod merge_tests { assert_eq!(expected_label, actual_vertex.label()) } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_v_anonymous_traversal)] fn test_merge_v_anonymous_traversal(client: GremlinClient) { let expected_label = "test_merge_v_anonymous_traversal"; @@ -162,7 +170,7 @@ mod merge_tests { assert_eq!(expected_label, actual_vertex.label()) } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_e_start_step)] fn test_merge_e_start_step(client: GremlinClient) { let expected_vertex_label = "test_merge_e_start_step_vertex"; @@ -215,6 +223,7 @@ mod merge_tests { let incoming_vertex_id = incoming_vertex .get("id") + .or(incoming_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); @@ -225,11 +234,12 @@ mod merge_tests { .unwrap(); let outgoing_vertex_id = outgoing_vertex .get("id") + .or(outgoing_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_e_no_options)] fn test_merge_e_no_options(client: GremlinClient) { let expected_vertex_label = "test_merge_e_no_options_vertex"; @@ -284,6 +294,7 @@ mod merge_tests { .unwrap(); let incoming_vertex_id = incoming_vertex .get("id") + .or(incoming_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); @@ -294,11 +305,12 @@ mod merge_tests { .unwrap(); let outgoing_vertex_id = outgoing_vertex .get("id") + .or(outgoing_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_e_options)] fn test_merge_e_options(client: GremlinClient) { let expected_vertex_label = "test_merge_e_options_vertex"; @@ -368,7 +380,7 @@ mod merge_tests { ); } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_e_anonymous_traversal)] fn test_merge_e_anonymous_traversal(client: GremlinClient) { let expected_vertex_label = "test_merge_e_options_vertex"; @@ -409,6 +421,7 @@ mod merge_tests { .unwrap(); let incoming_vertex_id = incoming_vertex .get("id") + .or(incoming_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(incoming_vertex_id, &vertex_a.id().to_gvalue()); @@ -419,11 +432,12 @@ mod merge_tests { .unwrap(); let outgoing_vertex_id = outgoing_vertex .get("id") + .or(outgoing_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } - #[apply(serializers)] + #[apply(merge_capable_serializers)] #[serial(test_merge_v_into_merge_e)] fn test_merge_v_into_merge_e(client: GremlinClient) { //Based on the reference doc's combo example @@ -472,6 +486,7 @@ mod merge_tests { .unwrap(); let brandy_vertex_id = brandy_vertex .get("id") + .or(brandy_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(*brandy_vertex_id, GValue::Int64(expected_brandy_id)); @@ -482,6 +497,7 @@ mod merge_tests { .unwrap(); let toby_vertex_id = toby_vertex .get("id") + .or(toby_vertex.get(T::Id)) .expect("Should have returned vertex id"); assert_eq!(*toby_vertex_id, GValue::Int64(expected_toby_id));