diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index daece672..92decde8 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -52,29 +52,29 @@ jobs: uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime + args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime,derive - name: Run cargo test with async-std if: matrix.gremlin-server == '3.5.7' uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path gremlin-client/Cargo.toml --features=async-std-runtime + args: --manifest-path gremlin-client/Cargo.toml --features=async-std-runtime,derive # MergeV as a step doesn't exist in 3.5.x, so selectively run those tests - name: Run cargo test with blocking client if: matrix.gremlin-server != '3.5.7' uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path gremlin-client/Cargo.toml --features=merge_tests + args: --manifest-path gremlin-client/Cargo.toml --features=merge_tests,derive - name: Run cargo test with tokio if: matrix.gremlin-server != '3.5.7' uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime,merge_tests + args: --manifest-path gremlin-client/Cargo.toml --features=tokio-runtime,merge_tests,derive - name: Run cargo test with async-std if: matrix.gremlin-server != '3.5.7' uses: actions-rs/cargo@v1 with: command: test - args: --manifest-path gremlin-client/Cargo.toml --features=async-std-runtime,merge_tests + args: --manifest-path gremlin-client/Cargo.toml --features=async-std-runtime,merge_tests,derive diff --git a/docker-compose/docker-compose.yaml b/docker-compose/docker-compose.yaml index 38817ac5..7bcd21e5 100644 --- a/docker-compose/docker-compose.yaml +++ b/docker-compose/docker-compose.yaml @@ -26,6 +26,8 @@ services: - JANUS_PROPS_TEMPLATE=inmemory ports: - "8184:8182" + volumes: + - ./janusgraph_config.yaml:/opt/janusgraph/conf/janusgraph-server.yaml healthcheck: test: ["CMD", "bin/gremlin.sh", "-e", "scripts/remote-connect.groovy"] interval: 10s diff --git a/docker-compose/janusgraph_config.yaml b/docker-compose/janusgraph_config.yaml new file mode 100644 index 00000000..811803c1 --- /dev/null +++ b/docker-compose/janusgraph_config.yaml @@ -0,0 +1,54 @@ +# Copyright 2019 JanusGraph Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +host: 0.0.0.0 +port: 8182 +evaluationTimeout: 30000 +channelizer: org.apache.tinkerpop.gremlin.server.channel.WebSocketChannelizer +graphManager: org.janusgraph.graphdb.management.JanusGraphManager +graphs: { + graph: conf/janusgraph-inmemory.properties +} +scriptEngines: { + gremlin-groovy: { + plugins: { org.janusgraph.graphdb.tinkerpop.plugin.JanusGraphGremlinPlugin: {}, + org.apache.tinkerpop.gremlin.server.jsr223.GremlinServerGremlinPlugin: {}, + org.apache.tinkerpop.gremlin.tinkergraph.jsr223.TinkerGraphGremlinPlugin: {}, + org.apache.tinkerpop.gremlin.jsr223.ImportGremlinPlugin: {classImports: [java.lang.Math], methodImports: [java.lang.Math#*]}, + org.apache.tinkerpop.gremlin.jsr223.ScriptFileGremlinPlugin: {files: [scripts/empty-sample.groovy]}}}} +serializers: + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }} + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphBinaryMessageSerializerV1, config: { serializeResultToString: true }} + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV3, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }} + # Older serialization versions for backwards compatibility: + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV2, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistry] }} + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONMessageSerializerV1, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistryV1d0] }} + - { className: org.apache.tinkerpop.gremlin.util.ser.GraphSONUntypedMessageSerializerV1, config: { ioRegistries: [org.janusgraph.graphdb.tinkerpop.JanusGraphIoRegistryV1d0] }} +processors: + - { className: org.apache.tinkerpop.gremlin.server.op.session.SessionOpProcessor, config: { sessionTimeout: 28800000 }} + - { className: org.apache.tinkerpop.gremlin.server.op.traversal.TraversalOpProcessor, config: { cacheExpirationTime: 600000, cacheMaxSize: 1000 }} +metrics: { + consoleReporter: {enabled: true, interval: 180000}, + csvReporter: {enabled: true, interval: 180000, fileName: /tmp/gremlin-server-metrics.csv}, + jmxReporter: {enabled: true}, + slf4jReporter: {enabled: true, interval: 180000}, + graphiteReporter: {enabled: false, interval: 180000}} +maxInitialLineLength: 4096 +maxHeaderSize: 8192 +maxChunkSize: 8192 +maxContentLength: 65536 +maxAccumulationBufferComponents: 1024 +resultIterationBatchSize: 64 +writeBufferLowWaterMark: 32768 +writeBufferHighWaterMark: 65536 \ No newline at end of file diff --git a/gremlin-cli/src/actions/connect.rs b/gremlin-cli/src/actions/connect.rs index d37cb05b..bd4ab859 100644 --- a/gremlin-cli/src/actions/connect.rs +++ b/gremlin-cli/src/actions/connect.rs @@ -1,6 +1,6 @@ use crate::{actions::Action, command::Command, context::GremlinContext}; use futures::FutureExt; -use gremlin_client::{aio::GremlinClient, ConnectionOptions, GraphSON, TlsOptions}; +use gremlin_client::{aio::GremlinClient, ConnectionOptions, IoProtocol, TlsOptions}; use std::str::FromStr; use structopt::StructOpt; @@ -30,11 +30,11 @@ impl FromStr for Serializer { } } -impl From for GraphSON { +impl From for IoProtocol { fn from(serializer: Serializer) -> Self { match serializer { - Serializer::GraphSONV2 => GraphSON::V2, - Serializer::GraphSONV3 => GraphSON::V3, + Serializer::GraphSONV2 => IoProtocol::GraphSONV2, + Serializer::GraphSONV3 => IoProtocol::GraphSONV3, } } } diff --git a/gremlin-client/Cargo.toml b/gremlin-client/Cargo.toml index e3b6dfa4..dc66d561 100644 --- a/gremlin-client/Cargo.toml +++ b/gremlin-client/Cargo.toml @@ -69,8 +69,10 @@ tokio = { version = "1", optional=true, features = ["full"] } features = ["serde", "v4"] version = "1.1.2" - - +[dev-dependencies] +rstest = "0.23.0" +rstest_reuse = "0.7.0" +serial_test = "3.1.1" [[example]] name = "traversal_async" diff --git a/gremlin-client/src/aio/client.rs b/gremlin-client/src/aio/client.rs index ad3c52f2..fbd3286c 100644 --- a/gremlin-client/src/aio/client.rs +++ b/gremlin-client/src/aio/client.rs @@ -1,9 +1,5 @@ use crate::aio::pool::GremlinConnectionManager; use crate::aio::GResultSet; -use crate::io::GraphSON; -use crate::message::{ - message_with_args, message_with_args_and_uuid, message_with_args_v2, Message, -}; use crate::process::traversal::Bytecode; use crate::GValue; use crate::ToGValue; @@ -11,8 +7,8 @@ use crate::{ConnectionOptions, GremlinError, GremlinResult}; use base64::encode; use futures::future::{BoxFuture, FutureExt}; use mobc::{Connection, Pool}; -use serde::Serialize; use std::collections::{HashMap, VecDeque}; +use uuid::Uuid; pub type SessionedClient = GremlinClient; @@ -21,18 +17,14 @@ impl SessionedClient { if let Some(session_name) = self.session.take() { let mut args = HashMap::new(); args.insert(String::from("session"), GValue::from(session_name.clone())); - let args = self.options.serializer.write(&GValue::from(args))?; - - let processor = "session".to_string(); - - let message = match self.options.serializer { - GraphSON::V2 => message_with_args_v2(String::from("close"), processor, args), - GraphSON::V3 => message_with_args(String::from("close"), processor, args), - }; + let (id, message) = self + .options + .serializer + .build_message("close", "session", args, None)?; let conn = self.pool.get().await?; - self.send_message_new(conn, message).await + self.send_message_new(conn, id, message).await } else { Err(GremlinError::Generic("No session to close".to_string())) } @@ -131,47 +123,38 @@ impl GremlinClient { args.insert(String::from("session"), GValue::from(session_name.clone())); } - let args = self.options.serializer.write(&GValue::from(args))?; - let processor = if self.session.is_some() { - "session".to_string() + "session" } else { - String::default() + "" }; - let message = match self.options.serializer { - GraphSON::V2 => message_with_args_v2(String::from("eval"), processor, args), - GraphSON::V3 => message_with_args(String::from("eval"), processor, args), - }; + let (id, message) = self + .options + .serializer + .build_message("eval", processor, args, None)?; let conn = self.pool.get().await?; - self.send_message_new(conn, message).await + self.send_message_new(conn, id, message).await } - pub(crate) fn send_message_new<'a, T: Serialize>( + pub(crate) fn send_message_new<'a>( &'a self, mut conn: Connection, - msg: Message, + id: Uuid, + binary: Vec, ) -> BoxFuture<'a, GremlinResult> { - let id = msg.id().clone(); - let message = self.build_message(msg).unwrap(); - async move { - let content_type = self.options.serializer.content_type(); - let payload = String::from("") + content_type + &message; - let mut binary = payload.into_bytes(); - binary.insert(0, content_type.len() as u8); - let (response, receiver) = conn.send(id, binary).await?; let (response, results) = match response.status.code { 200 | 206 => { - let results: VecDeque = self - .options - .deserializer - .read(&response.result.data)? - .map(|v| v.into()) + let results: VecDeque = response + .result + .data + .clone() + .map(Into::into) .unwrap_or_else(VecDeque::new); Ok((response, results)) } @@ -185,15 +168,18 @@ impl GremlinClient { GValue::String(encode(&format!("\0{}\0{}", c.username, c.password))), ); - let args = self.options.serializer.write(&GValue::from(args))?; - let message = message_with_args_and_uuid( - String::from("authentication"), - String::from("traversal"), - response.request_id, + let (id, message) = self.options.serializer.build_message( + "authentication", + "traversal", args, - ); - - return self.send_message_new(conn, message).await; + Some( + response + .request_id + .expect("Auth challenge requires response id"), + ), + )?; + + return self.send_message_new(conn, id, message).await; } None => Err(GremlinError::Request(( response.status.code, @@ -229,16 +215,13 @@ impl GremlinClient { args.insert(String::from("aliases"), GValue::from(aliases)); - let args = self.options.serializer.write(&GValue::from(args))?; - - let message = message_with_args(String::from("bytecode"), String::from("traversal"), args); + let (id, message) = + self.options + .serializer + .build_message("bytecode", "traversal", args, None)?; let conn = self.pool.get().await?; - self.send_message_new(conn, message).await - } - - fn build_message(&self, msg: Message) -> GremlinResult { - serde_json::to_string(&msg).map_err(GremlinError::from) + self.send_message_new(conn, id, message).await } } diff --git a/gremlin-client/src/aio/connection.rs b/gremlin-client/src/aio/connection.rs index a0f311e0..7854f0e5 100644 --- a/gremlin-client/src/aio/connection.rs +++ b/gremlin-client/src/aio/connection.rs @@ -1,4 +1,4 @@ -use crate::{GremlinError, GremlinResult, WebSocketOptions}; +use crate::{GremlinError, GremlinResult, IoProtocol}; use crate::connection::ConnectionOptions; @@ -165,7 +165,7 @@ impl Conn { sender_loop(sink, requests.clone(), receiver); - receiver_loop(stream, requests.clone(), sender.clone()); + receiver_loop(stream, requests.clone(), sender.clone(), opts.deserializer); Ok(Conn { sender, @@ -266,6 +266,7 @@ fn receiver_loop( mut stream: SplitStream, requests: Arc>>>>, mut sender: Sender, + deserializer: IoProtocol, ) { task::spawn(async move { loop { @@ -283,10 +284,24 @@ fn receiver_loop( } Some(Ok(item)) => match item { Message::Binary(data) => { - let response: Response = serde_json::from_slice(&data).unwrap(); + let response = deserializer + .read_response(data) + .expect("Unable to parse message"); let mut guard = requests.lock().await; + + //GraphBinary permits a null response request id, so in lieu of a request id assume + //a single entry in the requests to be the one we should respond to given connection + //multiplexing isn't currently implemented + let request_id = response.request_id.unwrap_or_else(|| { + if guard.len() == 1 { + guard.keys().next().expect("Should have had only 1 key").clone() + } else { + panic!("Request response without request id was received, but there isn't only 1 request currently submitted"); + } + }); + if response.status.code != 206 { - let item = guard.remove(&response.request_id); + let item = guard.remove(&request_id); drop(guard); if let Some(mut s) = item { match s.send(Ok(response)).await { @@ -295,7 +310,7 @@ fn receiver_loop( }; } } else { - let item = guard.get_mut(&response.request_id); + let item = guard.get_mut(&request_id); if let Some(s) = item { match s.send(Ok(response)).await { Ok(_r) => {} diff --git a/gremlin-client/src/aio/pool.rs b/gremlin-client/src/aio/pool.rs index 3d2ecfca..e4ca5c38 100644 --- a/gremlin-client/src/aio/pool.rs +++ b/gremlin-client/src/aio/pool.rs @@ -3,8 +3,7 @@ use mobc::Manager; use crate::aio::connection::Conn; use crate::connection::ConnectionOptions; use crate::error::GremlinError; -use crate::message::{message_with_args, message_with_args_and_uuid, message_with_args_v2}; -use crate::{GValue, GraphSON}; +use crate::GValue; use async_trait::async_trait; use base64::encode; use std::collections::HashMap; @@ -40,23 +39,13 @@ impl Manager for GremlinConnectionManager { String::from("language"), GValue::String(String::from("gremlin-groovy")), ); - let args = self.options.serializer.write(&GValue::from(args))?; - let message = match self.options.serializer { - GraphSON::V2 => message_with_args_v2(String::from("eval"), String::default(), args), - GraphSON::V3 => message_with_args(String::from("eval"), String::default(), args), - }; + let (id, message) = self + .options + .serializer + .build_message("eval", "", args, None)?; - let id = message.id().clone(); - let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; - - let content_type = self.options.serializer.content_type(); - - let payload = String::from("") + content_type + &msg; - let mut binary = payload.into_bytes(); - binary.insert(0, content_type.len() as u8); - - let (response, _receiver) = conn.send(id, binary).await?; + let (response, _receiver) = conn.send(id, message).await?; match response.status.code { 200 | 206 => Ok(conn), @@ -70,25 +59,17 @@ impl Manager for GremlinConnectionManager { GValue::String(encode(&format!("\0{}\0{}", c.username, c.password))), ); - let args = self.options.serializer.write(&GValue::from(args))?; - let message = message_with_args_and_uuid( - String::from("authentication"), - String::from("traversal"), - response.request_id, + let (id, message) = self.options.serializer.build_message( + "authentication", + "traversal", args, - ); - - let id = message.id().clone(); - let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; - - let content_type = self.options.serializer.content_type(); - let payload = String::from("") + content_type + &msg; - - let mut binary = payload.into_bytes(); - binary.insert(0, content_type.len() as u8); - - let (response, _receiver) = conn.send(id, binary).await?; - + Some( + response + .request_id + .expect("Auth challenge requires response id"), + ), + )?; + let (response, _receiver) = conn.send(id, message).await?; match response.status.code { 200 | 206 => Ok(conn), 204 => Ok(conn), diff --git a/gremlin-client/src/aio/result.rs b/gremlin-client/src/aio/result.rs index ddcb195c..daed9fc6 100644 --- a/gremlin-client/src/aio/result.rs +++ b/gremlin-client/src/aio/result.rs @@ -59,14 +59,12 @@ impl Stream for GResultSet { if this.response.status.code == 206 { match futures::ready!(this.receiver.as_mut().poll_next(cx)) { Some(Ok(response)) => { - let results: VecDeque = this - .client - .options - .serializer - .read(&response.result.data)? - .map(|v| v.into()) + let results: VecDeque = response + .result + .data + .clone() + .map(Into::into) .unwrap_or_else(VecDeque::new); - *this.results = results; *this.response = response; } diff --git a/gremlin-client/src/client.rs b/gremlin-client/src/client.rs index 8a24a8df..98535de3 100644 --- a/gremlin-client/src/client.rs +++ b/gremlin-client/src/client.rs @@ -1,7 +1,4 @@ -use crate::io::GraphSON; -use crate::message::{ - message_with_args, message_with_args_and_uuid, message_with_args_v2, Message, Response, -}; +use crate::message::Response; use crate::pool::GremlinConnectionManager; use crate::process::traversal::Bytecode; use crate::ToGValue; @@ -9,7 +6,6 @@ use crate::{ConnectionOptions, GremlinError, GremlinResult}; use crate::{GResultSet, GValue}; use base64::encode; use r2d2::Pool; -use serde::Serialize; use std::collections::{HashMap, VecDeque}; type SessionedClient = GremlinClient; @@ -19,14 +15,11 @@ impl SessionedClient { if let Some(session_name) = self.session.take() { let mut args = HashMap::new(); args.insert(String::from("session"), GValue::from(session_name.clone())); - let args = self.options.serializer.write(&GValue::from(args))?; - let processor = "session".to_string(); - - let message = match self.options.serializer { - GraphSON::V2 => message_with_args_v2(String::from("close"), processor, args), - GraphSON::V3 => message_with_args(String::from("close"), processor, args), - }; + let (_, message) = self + .options + .serializer + .build_message("close", "session", args, None)?; let conn = self.pool.get()?; @@ -127,62 +120,35 @@ impl GremlinClient { args.insert(String::from("session"), GValue::from(session_name.clone())); } - let args = self.options.serializer.write(&GValue::from(args))?; - let processor = if self.session.is_some() { - "session".to_string() + "session" } else { - String::default() + "" }; - let message = match self.options.serializer { - GraphSON::V2 => message_with_args_v2(String::from("eval"), processor, args), - GraphSON::V3 => message_with_args(String::from("eval"), processor, args), - }; + let (_, message) = self + .options + .serializer + .build_message("eval", processor, args, None)?; let conn = self.pool.get()?; self.send_message(conn, message) } - pub(crate) fn write_message( - &self, - conn: &mut r2d2::PooledConnection, - msg: Message, - ) -> GremlinResult<()> { - let message = self.build_message(msg)?; - - let content_type = self.options.serializer.content_type(); - let payload = String::from("") + content_type + &message; - - let mut binary = payload.into_bytes(); - binary.insert(0, content_type.len() as u8); - - conn.send(binary)?; - - Ok(()) - } - - pub(crate) fn send_message( + pub(crate) fn send_message( &self, mut conn: r2d2::PooledConnection, - msg: Message, + msg: Vec, ) -> GremlinResult { - self.write_message(&mut conn, msg)?; + conn.send(msg)?; let (response, results) = self.read_response(&mut conn)?; Ok(GResultSet::new(self.clone(), results, response, conn)) } - pub fn generate_message( - &self, - bytecode: &Bytecode, - ) -> GremlinResult> { - let mut args = HashMap::new(); - - args.insert(String::from("gremlin"), GValue::Bytecode(bytecode.clone())); - + pub(crate) fn submit_traversal(&self, bytecode: &Bytecode) -> GremlinResult { let aliases = self .alias .clone() @@ -194,20 +160,14 @@ impl GremlinClient { }) .unwrap_or_else(HashMap::new); + let mut args = HashMap::new(); + args.insert(String::from("gremlin"), GValue::Bytecode(bytecode.clone())); args.insert(String::from("aliases"), GValue::from(aliases)); - let args = self.options.serializer.write(&GValue::from(args))?; - - Ok(message_with_args( - String::from("bytecode"), - String::from("traversal"), - args, - )) - } - - pub(crate) fn submit_traversal(&self, bytecode: &Bytecode) -> GremlinResult { - let message = self.generate_message(bytecode)?; - + let (_, message) = + self.options + .serializer + .build_message("bytecode", "traversal", args, None)?; let conn = self.pool.get()?; self.send_message(conn, message) @@ -218,15 +178,15 @@ impl GremlinClient { conn: &mut r2d2::PooledConnection, ) -> GremlinResult<(Response, VecDeque)> { let result = conn.recv()?; - let response: Response = serde_json::from_slice(&result)?; + let response = self.options.deserializer.read_response(result)?; match response.status.code { 200 | 206 => { - let results: VecDeque = self - .options - .deserializer - .read(&response.result.data)? - .map(|v| v.into()) + let results: VecDeque = response + .result + .data + .clone() + .map(Into::into) .unwrap_or_else(VecDeque::new); Ok((response, results)) @@ -241,15 +201,17 @@ impl GremlinClient { GValue::String(encode(&format!("\0{}\0{}", c.username, c.password))), ); - let args = self.options.serializer.write(&GValue::from(args))?; - let message = message_with_args_and_uuid( - String::from("authentication"), - String::from("traversal"), - response.request_id, + let (_, message) = self.options.serializer.build_message( + "authentication", + "traversal", args, - ); - - self.write_message(conn, message)?; + Some( + response + .request_id + .expect("Auth challenge requires response id"), + ), + )?; + conn.send(message)?; self.read_response(conn) } @@ -264,7 +226,4 @@ impl GremlinClient { ))), } } - fn build_message(&self, msg: Message) -> GremlinResult { - serde_json::to_string(&msg).map_err(GremlinError::from) - } } diff --git a/gremlin-client/src/connection.rs b/gremlin-client/src/connection.rs index 186d06ca..56854c90 100644 --- a/gremlin-client/src/connection.rs +++ b/gremlin-client/src/connection.rs @@ -1,6 +1,6 @@ -use std::{net::TcpStream, sync::Arc, time::Duration}; +use std::{net::TcpStream, time::Duration}; -use crate::{GraphSON, GremlinError, GremlinResult}; +use crate::{GremlinError, GremlinResult, IoProtocol}; use native_tls::TlsConnector; use tungstenite::{ client::{uri_mode, IntoClientRequest}, @@ -164,12 +164,12 @@ impl ConnectionOptionsBuilder { self } - pub fn serializer(mut self, serializer: GraphSON) -> Self { + pub fn serializer(mut self, serializer: IoProtocol) -> Self { self.0.serializer = serializer; self } - pub fn deserializer(mut self, deserializer: GraphSON) -> Self { + pub fn deserializer(mut self, deserializer: IoProtocol) -> Self { self.0.deserializer = deserializer; self } @@ -185,8 +185,8 @@ pub struct ConnectionOptions { pub(crate) credentials: Option, pub(crate) ssl: bool, pub(crate) tls_options: Option, - pub(crate) serializer: GraphSON, - pub(crate) deserializer: GraphSON, + pub(crate) serializer: IoProtocol, + pub(crate) deserializer: IoProtocol, pub(crate) websocket_options: Option, } @@ -269,8 +269,8 @@ impl Default for ConnectionOptions { credentials: None, ssl: false, tls_options: None, - serializer: GraphSON::V3, - deserializer: GraphSON::V3, + serializer: IoProtocol::GraphSONV3, + deserializer: IoProtocol::GraphSONV3, websocket_options: None, } } diff --git a/gremlin-client/src/conversion.rs b/gremlin-client/src/conversion.rs index fd2102dd..03333ff3 100644 --- a/gremlin-client/src/conversion.rs +++ b/gremlin-client/src/conversion.rs @@ -129,8 +129,6 @@ impl_from_gvalue!(bool, GValue::Bool); impl_from_gvalue!(uuid::Uuid, GValue::Uuid); impl_from_gvalue!(Metric, GValue::Metric); impl_from_gvalue!(TraversalMetrics, GValue::TraversalMetrics); -impl_from_gvalue!(TraversalExplanation, GValue::TraversalExplanation); -impl_from_gvalue!(IntermediateRepr, GValue::IntermediateRepr); impl_from_gvalue!(chrono::DateTime, GValue::Date); impl_from_gvalue!(Traverser, GValue::Traverser); @@ -139,9 +137,90 @@ impl FromGValue for Null { match v { GValue::Null => Ok(crate::structure::Null {}), _ => Err(GremlinError::Cast(format!( - "Cannot convert {:?} to {}", + "Cannot convert {:?} to Null", v, - stringify!($t) + ))), + } + } +} + +impl FromGValue for TraversalExplanation { + fn from_gvalue(v: GValue) -> GremlinResult { + match v { + GValue::TraversalExplanation(traversal_explaination) => Ok(traversal_explaination), + //GraphBinary sends TraversalExplainations as just a map instead of as a distinct type, + //so handle converting it here + GValue::Map(mut map) => { + let original = map.remove("original").ok_or(GremlinError::Cast(format!( + "Missing expected \"original\" TraversalExplaination property" + )))?; + let intermediate = + map.remove("intermediate") + .ok_or(GremlinError::Cast(format!( + "Missing expected \"intermediate\" TraversalExplaination property" + )))?; + let final_ = map.remove("final").ok_or(GremlinError::Cast(format!( + "Missing expected \"final\" TraversalExplaination property" + )))?; + + let original = ::from_gvalue(original)?; + let intermediate = ::from_gvalue(intermediate)?; + let final_ = ::from_gvalue(final_)?; + + let original: GremlinResult> = original + .into_iter() + .map(|val| ::from_gvalue(val)) + .collect(); + let final_: GremlinResult> = final_ + .into_iter() + .map(|val| ::from_gvalue(val)) + .collect(); + let intermediate: GremlinResult> = intermediate + .into_iter() + .map(|val| IntermediateRepr::from_gvalue(val)) + .collect(); + + Ok(TraversalExplanation::new(original?, final_?, intermediate?)) + } + _ => Err(GremlinError::Cast(format!( + "Cannot convert {:?} to TraversalExplanation", + v + ))), + } + } +} + +impl FromGValue for IntermediateRepr { + fn from_gvalue(v: GValue) -> GremlinResult { + match v { + GValue::IntermediateRepr(ir) => Ok(ir), + //GraphBinary sends TraversalExplainations as just a map instead of as a distinct type, + //so handle converting it here + GValue::Map(mut map) => { + let traversal = map.remove("traversal").ok_or(GremlinError::Cast(format!( + "Missing expected \"traversal\" IntermediateRepr property" + )))?; + let category = map.remove("category").ok_or(GremlinError::Cast(format!( + "Missing expected \"category\" IntermediateRepr property" + )))?; + let strategy = map.remove("strategy").ok_or(GremlinError::Cast(format!( + "Missing expected \"strategy\" IntermediateRepr property" + )))?; + + let traversal: GremlinResult> = + ::from_gvalue(traversal)? + .into_iter() + .map(|val| ::from_gvalue(val)) + .collect(); + Ok(IntermediateRepr::new( + traversal?, + ::from_gvalue(strategy)?, + ::from_gvalue(category)?, + )) + } + _ => Err(GremlinError::Cast(format!( + "Cannot convert {:?} to IntermediateRepr", + v ))), } } @@ -155,6 +234,9 @@ impl FromGValue for GKey { GValue::Token(s) => Ok(GKey::String(s.value().clone())), GValue::Vertex(s) => Ok(GKey::Vertex(s)), GValue::Edge(s) => Ok(GKey::Edge(s)), + GValue::Int64(v) => Ok(GKey::Int64(v)), + GValue::Int32(v) => Ok(GKey::Int32(v)), + GValue::T(t) => Ok(GKey::T(t)), _ => Err(GremlinError::Cast(format!( "Cannot convert {:?} to {}", v, "GKey" diff --git a/gremlin-client/src/io/graph_binary_v1.rs b/gremlin-client/src/io/graph_binary_v1.rs new file mode 100644 index 00000000..db7cdd9c --- /dev/null +++ b/gremlin-client/src/io/graph_binary_v1.rs @@ -0,0 +1,1308 @@ +use std::{collections::HashMap, convert::TryInto}; + +use chrono::{DateTime, TimeZone, Utc}; +use uuid::Uuid; + +use crate::{ + conversion::FromGValue, + io::graph_binary_v1, + message::{ReponseStatus, Response, ResponseResult}, + process::traversal::{Instruction, Order, Scope}, + structure::{Column, Direction, Merge, Pop, TextP, Traverser, T}, + Cardinality, Edge, GKey, GValue, GremlinError, GremlinResult, Metric, Path, Property, ToGValue, + TraversalMetrics, Vertex, VertexProperty, GID, +}; + +use super::IoProtocol; + +const VERSION_BYTE: u8 = 0x81; +const VALUE_FLAG: u8 = 0x00; +const VALUE_NULL_FLAG: u8 = 0x01; + +//Data codes (https://tinkerpop.apache.org/docs/current/dev/io/#_data_type_codes) +const INTEGER: u8 = 0x01; +const LONG: u8 = 0x02; +const STRING: u8 = 0x03; +const DATE: u8 = 0x04; +// const TIMESTAMP: u8 = 0x05; +// const CLASS: u8 = 0x06; +const DOUBLE: u8 = 0x07; +const FLOAT: u8 = 0x08; +const LIST: u8 = 0x09; +const MAP: u8 = 0x0A; +const SET: u8 = 0x0B; +const UUID: u8 = 0x0C; +const EDGE: u8 = 0x0D; +const PATH: u8 = 0x0E; +const PROPERTY: u8 = 0x0F; +// const TINKERGRAPH: u8 = 0x10; +const VERTEX: u8 = 0x11; +const VERTEX_PROPERTY: u8 = 0x12; +// const BARRIER: u8 = 0x13; +// const BINDING: u8 = 0x14; +const BYTECODE: u8 = 0x15; +const CARDINALITY: u8 = 0x16; +const COLUMN: u8 = 0x17; +const DIRECTION: u8 = 0x18; +// const OPERATOR: u8 = 0x19; +const ORDER: u8 = 0x1A; +// const PICK: u8 = 0x1B; +const POP: u8 = 0x1C; +// const LAMBDA: u8 = 0x1D; +const P: u8 = 0x1E; +const SCOPE: u8 = 0x1F; +const T: u8 = 0x20; +const TRAVERSER: u8 = 0x21; +const BOOLEAN: u8 = 0x27; +const TEXTP: u8 = 0x28; +const MERTRICS: u8 = 0x2C; +const TRAVERSAL_MERTRICS: u8 = 0x2D; +const MERGE: u8 = 0x2E; +const UNSPECIFIED_NULL_OBEJECT: u8 = 0xFE; +const CUSTOM: u8 = 0x00; + +pub(crate) struct RequestMessage<'a, 'b> { + pub(crate) request_id: Uuid, + pub(crate) op: &'a str, + pub(crate) processor: &'b str, + pub(crate) args: HashMap, +} + +pub(crate) struct ResponseMessage { + //Format: {version}{request_id}{status_code}{status_message}{status_attributes}{result_meta}{result_data} + pub(crate) request_id: Option, + pub(crate) status_code: i16, + pub(crate) status_message: String, + pub(crate) status_attributes: HashMap, + pub(crate) result_meta: HashMap, + pub(crate) result_data: Option, +} + +impl Into for ResponseMessage { + fn into(self) -> Response { + let status = ReponseStatus { + code: self.status_code, + message: self.status_message, + }; + Response { + request_id: self.request_id, + result: ResponseResult { + data: self.result_data, + }, + status, + } + } +} + +impl GraphBinaryV1Deser for HashMap { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //first will be the map length + let map_length = ::from_be_bytes(bytes)?; + let mut map = HashMap::new(); + //Then fully qualified entry of each k/v pair + for _ in 0..map_length { + let key: GKey = GKey::from_gvalue(GValue::from_be_bytes(bytes)?) + .map_err(|_| GremlinError::Cast(format!("Invalid GKey bytes")))?; + let value = GValue::from_be_bytes(bytes)?; + + map.insert(key, value); + } + Ok(map) + } +} + +impl GraphBinaryV1Deser for ResponseMessage { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //First confirm the version is as expected + let Some(&graph_binary_v1::VERSION_BYTE) = bytes.next() else { + return Err(GremlinError::Cast(format!("Invalid version byte"))); + }; + + //Request id is nullable + let request_id = Uuid::from_be_bytes_nullable(bytes)?; + + let status_code = ::from_be_bytes(bytes)? + .try_into() + .expect("Status code should fit in i16"); + //Status message is nullable + let status_message = String::from_be_bytes_nullable(bytes)?.unwrap_or_default(); + + let status_attributes = GraphBinaryV1Deser::from_be_bytes(bytes)?; + let result_meta: HashMap = GraphBinaryV1Deser::from_be_bytes(bytes)?; + let result_data = GValue::from_be_bytes(bytes)?; + let result_data = if result_data == GValue::Null { + None + } else { + Some(result_data) + }; + Ok(ResponseMessage { + request_id, + status_code, + status_message, + status_attributes, + result_meta, + result_data, + }) + } +} + +impl<'a, 'b> GraphBinaryV1Ser for RequestMessage<'a, 'b> { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Need to write header first, its length is a Byte not a Int + let header = IoProtocol::GraphBinaryV1.content_type(); + let header_length: u8 = header + .len() + .try_into() + .expect("Header length should fit in u8"); + buf.push(header_length); + buf.extend_from_slice(header.as_bytes()); + + //Version byte first + buf.push(VERSION_BYTE); + + //Request Id + self.request_id.to_be_bytes(buf)?; + + //Op + self.op.to_be_bytes(buf)?; + + //Processor + self.processor.to_be_bytes(buf)?; + + //Args + let args_length: i32 = self + .args + .len() + .try_into() + .map_err(|_| GremlinError::Cast(format!("Args exceeds i32 length limit")))?; + GraphBinaryV1Ser::to_be_bytes(args_length, buf)?; + for (k, v) in self.args.into_iter() { + //Both keys and values need to be fully qualified here, so turn + //the keys into a GValue + GValue::from(k).to_be_bytes(buf)?; + v.to_be_bytes(buf)?; + } + Ok(()) + } +} + +//https://tinkerpop.apache.org/docs/current/dev/io/#_data_type_codes +//Each type has a "fully qualified" serialized form usually: {type_code}{type_info}{value_flag}{value} +//{type_code} is a single unsigned byte representing the type number. +//{type_info} is an optional sequence of bytes providing additional information of the type represented. This is specially useful for representing complex and custom types. +//{value_flag} is a single byte providing information about the value. Flags have the following meaning: +// 0x01 The value is null. When this flag is set, no bytes for {value} will be provided. +//{value} is a sequence of bytes which content is determined by the type. +//All encodings are big-endian. + +//However there are occassion when just "the value" is written without the fully qualified form, for example the 4 bytes of a integer without the type_code +//this is usually done in scenarios when the type in unambiguous by schema. + +//Generally this is written such that serializing a value wrapped by GValue is taken to mean to write the fully qualified representation +//and serializing just "the value" is done directly upon the underlying value type + +fn write_usize_as_i32_be_bytes(val: usize, buf: &mut Vec) -> GremlinResult<()> { + let val_i32 = TryInto::::try_into(val) + .map_err(|_| GremlinError::Cast(format!("Invalid usize bytes exceed i32")))?; + GraphBinaryV1Ser::to_be_bytes(val_i32, buf) +} + +impl GraphBinaryV1Ser for &GValue { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + match self { + GValue::Int32(value) => { + //Type code of 0x01 + buf.push(INTEGER); + //Empty value flag + buf.push(VALUE_FLAG); + //then value bytes + GraphBinaryV1Ser::to_be_bytes(*value, buf)?; + } + GValue::Int64(value) => { + buf.push(LONG); + buf.push(VALUE_FLAG); + GraphBinaryV1Ser::to_be_bytes(*value, buf)?; + } + GValue::String(value) => { + write_fully_qualified_str(value, buf)?; + } + GValue::Date(value) => { + buf.push(DATE); + buf.push(VALUE_FLAG); + value.to_be_bytes(buf)?; + } + GValue::Double(value) => { + buf.push(DOUBLE); + buf.push(VALUE_FLAG); + GraphBinaryV1Ser::to_be_bytes(*value, buf)?; + } + GValue::Float(value) => { + buf.push(FLOAT); + buf.push(VALUE_FLAG); + GraphBinaryV1Ser::to_be_bytes(*value, buf)?; + } + GValue::List(value) => { + buf.push(LIST); + buf.push(VALUE_FLAG); + + //{length} is an Int describing the length of the collection. + write_usize_as_i32_be_bytes(value.len(), buf)?; + + //{item_0}…​{item_n} are the items of the list. {item_i} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + for item in value.iter() { + item.to_be_bytes(buf)?; + } + } + GValue::Map(map) => { + //Type code of 0x0a: Map + buf.push(MAP); + // //Empty value flag + buf.push(VALUE_FLAG); + + //{length} is an Int describing the length of the map. + write_usize_as_i32_be_bytes(map.len(), buf)?; + + //{item_0}…​{item_n} are the items of the map. {item_i} is sequence of 2 fully qualified typed values one representing the key + // and the following representing the value, each composed of {type_code}{type_info}{value_flag}{value}. + for (k, v) in map.iter() { + k.to_be_bytes(buf)?; + v.to_be_bytes(buf)?; + } + } + GValue::Set(value) => { + buf.push(SET); + buf.push(VALUE_FLAG); + + //{length} is an Int describing the length of the collection. + write_usize_as_i32_be_bytes(value.len(), buf)?; + + //{item_0}…​{item_n} are the items of the list. {item_i} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + for item in value.iter() { + item.to_be_bytes(buf)?; + } + } + GValue::Uuid(value) => { + buf.push(UUID); + buf.push(VALUE_FLAG); + value.to_be_bytes(buf)?; + } + GValue::Vertex(vertex) => { + buf.push(VERTEX); + buf.push(VALUE_FLAG); + vertex.id().to_be_bytes(buf)?; + vertex.label().to_be_bytes(buf)?; + GValue::Null.to_be_bytes(buf)?; + } + GValue::Bytecode(code) => { + //Type code of 0x15: Bytecode + buf.push(BYTECODE); + //Empty value flag + buf.push(VALUE_FLAG); + //then value bytes + // {steps_length}{step_0}…​{step_n}{sources_length}{source_0}…​{source_n} + //{steps_length} is an Int value describing the amount of steps. + //{step_i} is composed of {name}{values_length}{value_0}…​{value_n}, where: + // {name} is a String. + // {values_length} is an Int describing the amount values. + // {value_i} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} describing the step argument. + + fn write_instructions( + instructions: &Vec, + buf: &mut Vec, + ) -> GremlinResult<()> { + write_usize_as_i32_be_bytes(instructions.len(), buf)?; + for instruction in instructions { + GraphBinaryV1Ser::to_be_bytes(instruction.operator().as_str(), buf)?; + write_usize_as_i32_be_bytes(instruction.args().len(), buf)?; + instruction + .args() + .iter() + .try_for_each(|arg| arg.to_be_bytes(buf))?; + } + Ok(()) + } + write_instructions(code.steps(), buf)?; + write_instructions(code.sources(), buf)?; + } + GValue::Cardinality(cardinality) => { + buf.push(CARDINALITY); + buf.push(VALUE_FLAG); + cardinality.to_be_bytes(buf)?; + } + GValue::Column(column) => { + buf.push(COLUMN); + buf.push(VALUE_FLAG); + column.to_be_bytes(buf)?; + } + GValue::Direction(direction) => { + buf.push(DIRECTION); + buf.push(VALUE_FLAG); + direction.to_be_bytes(buf)?; + } + GValue::Order(order) => { + buf.push(ORDER); + buf.push(VALUE_FLAG); + order.to_be_bytes(buf)?; + } + GValue::Pop(pop) => { + buf.push(POP); + buf.push(VALUE_FLAG); + pop.to_be_bytes(buf)?; + } + GValue::P(p) => { + //Type code of 0x1e: P + buf.push(P); + buf.push(VALUE_FLAG); + p.to_be_bytes(buf)?; + } + GValue::Scope(scope) => { + //Type code of 0x1f: Scope + buf.push(SCOPE); + //Empty value flag + buf.push(VALUE_FLAG); + + scope.to_be_bytes(buf)?; + } + GValue::T(t) => { + buf.push(T); + buf.push(VALUE_FLAG); + t.to_be_bytes(buf)?; + } + GValue::Bool(bool) => { + buf.push(BOOLEAN); + buf.push(VALUE_FLAG); + bool.to_be_bytes(buf)?; + } + GValue::TextP(text_p) => { + buf.push(TEXTP); + 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!("Serializing GValue {other:?}"), + } + Ok(()) + } +} + +fn predicate_to_be_bytes( + operator: &String, + value: &GValue, + buf: &mut Vec, +) -> GremlinResult<()> { + operator.to_be_bytes(buf)?; + match value { + //Singular values have a length of 1 + //But still need to be written fully qualified + scalar @ GValue::Uuid(_) + | scalar @ GValue::Int32(_) + | scalar @ GValue::Int64(_) + | scalar @ GValue::Float(_) + | scalar @ GValue::Double(_) + | scalar @ GValue::String(_) + | scalar @ GValue::Date(_) => { + GraphBinaryV1Ser::to_be_bytes(1i32, buf)?; + scalar.to_be_bytes(buf)?; + } + //"Collections" need to be unfurled, we don't write the collection but + //instead just its lengths and then the fully qualified form of each element + GValue::List(list) => { + write_usize_as_i32_be_bytes(list.len(), buf)?; + for item in list.iter() { + item.to_be_bytes(buf)?; + } + } + GValue::Set(set) => { + write_usize_as_i32_be_bytes(set.len(), buf)?; + for item in set.iter() { + item.to_be_bytes(buf)?; + } + } + other => unimplemented!("Predicate serialization of {other:?} not implemented"), + } + Ok(()) +} + +impl GraphBinaryV1Ser for &crate::structure::P { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + predicate_to_be_bytes(&self.operator, &self.value, buf) + } +} + +fn write_fully_qualified_str(value: &str, buf: &mut Vec) -> GremlinResult<()> { + //Extracted from the GValue implementation so it can be used by enum string values + //There are times we want the fully qualified prefix bytes outside of the normal + //GValue based route, without having to allocate the string again inside the GValue + + //Type code of 0x03: String + buf.push(STRING); + //Empty value flag + buf.push(VALUE_FLAG); + 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. + 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<()> { + 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<()> { + 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<()> { + 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. + 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. + 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) => 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:?}"), + } + } +} +pub trait GraphBinaryV1Ser: Sized { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()>; +} + +pub trait GraphBinaryV1Deser: Sized { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult; + + fn from_be_bytes_nullable<'a, S: Iterator>( + bytes: &mut S, + ) -> GremlinResult> { + match bytes.next().cloned() { + Some(VALUE_FLAG) => Self::from_be_bytes(bytes).map(Option::Some), + Some(VALUE_NULL_FLAG) => Ok(None), + other => { + return Err(GremlinError::Cast(format!( + "Unexpected byte for nullable check: {other:?}" + ))); + } + } + } +} + +impl GraphBinaryV1Ser for bool { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: A single byte containing the value 0x01 when it’s true and 0 otherwise. + match self { + true => buf.push(0x01), + false => buf.push(0x00), + } + Ok(()) + } +} + +impl GraphBinaryV1Ser for &TextP { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + predicate_to_be_bytes(&self.operator, &self.value, buf) + } +} + +impl GraphBinaryV1Deser for bool { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + match bytes.next() { + Some(0x00) => Ok(false), + Some(0x01) => Ok(true), + other => Err(GremlinError::Cast(format!( + "No boolean value byte {other:?}" + ))), + } + } +} + +impl GraphBinaryV1Deser for GValue { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + let data_code = bytes + .next() + .ok_or_else(|| GremlinError::Cast(format!("Invalid bytes no data code byte")))?; + match *data_code { + INTEGER => Ok(match i32::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Int32(value), + None => GValue::Null, + }), + LONG => Ok(match i64::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Int64(value), + None => GValue::Null, + }), + STRING => Ok(match String::from_be_bytes_nullable(bytes)? { + Some(string) => GValue::String(string), + None => GValue::Null, + }), + DATE => match i64::from_be_bytes_nullable(bytes)? { + Some(value) => match Utc.timestamp_millis_opt(value) { + chrono::LocalResult::Single(valid) => Ok(GValue::Date(valid)), + _ => Err(GremlinError::Cast(format!("Invalid timestamp millis"))), + }, + None => Ok(GValue::Null), + }, + DOUBLE => Ok(match f64::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Double(value), + None => GValue::Null, + }), + FLOAT => Ok(match f32::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Float(value), + None => GValue::Null, + }), + LIST => { + let deserialized_list: Option> = + GraphBinaryV1Deser::from_be_bytes_nullable(bytes)?; + Ok(deserialized_list + .map(|val| GValue::List(val.into())) + .unwrap_or(GValue::Null)) + } + MAP => { + let deserialized_map: Option> = + GraphBinaryV1Deser::from_be_bytes_nullable(bytes)?; + Ok(deserialized_map + .map(|val| GValue::Map(val.into())) + .unwrap_or(GValue::Null)) + } + SET => { + let deserialized_set: Option> = + GraphBinaryV1Deser::from_be_bytes_nullable(bytes)?; + Ok(deserialized_set + .map(|val| GValue::Set(val.into())) + .unwrap_or(GValue::Null)) + } + UUID => Ok(match Uuid::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Uuid(value), + None => GValue::Null, + }), + EDGE => Ok(match Edge::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Edge(value), + None => GValue::Null, + }), + PATH => Ok(match Path::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Path(value), + None => GValue::Null, + }), + PROPERTY => Ok(match Property::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Property(value), + None => GValue::Null, + }), + VERTEX => Ok(match Vertex::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Vertex(value), + None => GValue::Null, + }), + VERTEX_PROPERTY => Ok(match VertexProperty::from_be_bytes_nullable(bytes)? { + 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, + }), + TRAVERSER => { + let traverser: Option = + GraphBinaryV1Deser::from_be_bytes_nullable(bytes)?; + Ok(traverser + .map(|val| GValue::Traverser(val)) + .unwrap_or(GValue::Null)) + } + BOOLEAN => Ok(match bool::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Bool(value), + None => GValue::Null, + }), + MERTRICS => Ok(match Metric::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::Metric(value), + None => GValue::Null, + }), + TRAVERSAL_MERTRICS => Ok(match TraversalMetrics::from_be_bytes_nullable(bytes)? { + Some(value) => GValue::TraversalMetrics(value), + None => GValue::Null, + }), + UNSPECIFIED_NULL_OBEJECT => { + //Need to confirm the null-ness with the next byte being a 1 + match bytes.next().cloned() { + Some(VALUE_NULL_FLAG) => Ok(GValue::Null), + other => Err(GremlinError::Cast(format!( + "Not expected null value byte {other:?}" + ))), + } + } + CUSTOM => { + let custom_name = String::from_be_bytes(bytes)?; + match custom_name.as_str() { + "janusgraph.RelationIdentifier" => { + let deserialized: Option = + GraphBinaryV1Deser::from_be_bytes(bytes)?; + Ok(deserialized + .map(|value| { + //We don't have a GValue for JG types, and moreover supporting future custom types we may not want to + //so for now just mapping it to a GValue::String + let mut converted = format!( + "{}-{}-{}", + value.relation_id, value.out_vertex_id, value.type_id + ); + if let Some(in_vertex_id) = value.in_vertex_id { + converted.push('-'); + converted.push_str(&in_vertex_id); + } + GValue::String(converted) + }) + .unwrap_or(GValue::Null)) + } + other => unimplemented!("Unimplemented handling of custom type {other}"), + } + } + other => { + let remainder: Vec = bytes.cloned().collect(); + unimplemented!("Unimplemented deserialization byte {other}. {remainder:?}"); + } + } + } +} + +#[derive(Debug)] +struct JanusGraphRelationIdentifier { + out_vertex_id: String, + type_id: i64, + relation_id: i64, + in_vertex_id: Option, +} + +impl GraphBinaryV1Deser for Option { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Confirm the marker bytes that should be next + //0x1001 + let marker_bytes: i32 = GraphBinaryV1Deser::from_be_bytes(bytes)?; + if marker_bytes != 0x1001 { + return Err(GremlinError::Cast(format!( + "Unexpected marker bytes for JanusGraphRelationIdentifier" + ))); + } + + match bytes.next() { + Some(0x00) => { + //nothing to do + } + Some(0x01) => return Ok(None), + _ => return Err(GremlinError::Cast(format!("Invalid null value byte"))), + } + + fn read_string<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + let mut string = String::new(); + //JG custom string serialization uses the high portion of a byte to indicate the terminus of the string + //so we'll need to mask out the value from the low half of the byte and then check the high portion + //for termination + loop { + let Some(byte) = bytes.next() else { + return Err(GremlinError::Cast(format!( + "Exhausted bytes before terminal string marker" + ))); + }; + string.push((byte & 0x7F) as char); + + if byte & 0x80 > 0 { + break; + } + } + Ok(string) + } + + fn read_vertex_id<'a, S: Iterator>( + bytes: &mut S, + ) -> GremlinResult> { + //There should be a type marker of either Long(0) or String(1) + //reuse bool here, mapping false to a Long and true to String + if bool::from_be_bytes(bytes)? { + Ok(Some(read_string(bytes)?)) + } else { + let value = ::from_be_bytes(bytes)?; + if value == 0 { + Ok(None) + } else { + Ok(Some(value.to_string())) + } + } + } + + let out_vertex_id = read_vertex_id(bytes)?.expect("Out vertex id should never be null"); + let type_id: i64 = GraphBinaryV1Deser::from_be_bytes(bytes)?; + let relation_id: i64 = GraphBinaryV1Deser::from_be_bytes(bytes)?; + let in_vertex_id = read_vertex_id(bytes)?; + Ok(Some(JanusGraphRelationIdentifier { + out_vertex_id, + type_id, + relation_id, + in_vertex_id, + })) + } +} + +impl GraphBinaryV1Deser for TraversalMetrics { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: {duration}{metrics} + + //{duration} is a Long describing the duration in nanoseconds + let duration: i64 = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{metrics} is a List composed by Metrics items + let metrics: Vec = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + let metrics: Result, GremlinError> = metrics + .into_iter() + .map(|value| Metric::from_gvalue(value)) + .collect(); + + //It doesn't appear documented but assuming the duration unit inherited from GraphSON is ms, so convert here + let duration_ms = duration as f64 / 1_000.0; + Ok(TraversalMetrics::new(duration_ms, metrics?)) + } +} + +impl GraphBinaryV1Deser for Metric { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: {id}{name}{duration}{counts}{annotations}{nested_metrics} + + //{id} is a String representing the identifier + let id = String::from_be_bytes(bytes)?; + //{name} is a String representing the name + let name = String::from_be_bytes(bytes)?; + + //{duration} is a Long describing the duration in nanoseconds + let duration: i64 = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{counts} is a Map composed by String keys and Long values + let mut counts: HashMap = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{annotations} is a Map composed by String keys and a value of any type + let mut annotations: HashMap = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{nested_metrics} is a List composed by Metrics items + let nested = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + let traverser_count = + i64::from_gvalue(counts.remove(&GKey::from("traverserCount")).ok_or( + GremlinError::Cast(format!("Missing expected traverserCount property")), + )?)?; + + let element_count = i64::from_gvalue(counts.remove(&GKey::from("elementCount")).ok_or( + GremlinError::Cast(format!("Missing expected elementCount property")), + )?)?; + let percent_dur = f64::from_gvalue(annotations.remove(&GKey::from("percentDur")).ok_or( + GremlinError::Cast(format!("Missing expected percentDur property")), + )?)?; + + //It doesn't appear documented but assuming the duration unit inherited from GraphSON is ms, so convert here + let duration_ms = duration as f64 / 1_000.0; + + Ok(Metric::new( + id, + name, + duration_ms, + element_count, + traverser_count, + percent_dur, + nested, + )) + } +} + +impl GraphBinaryV1Deser for T { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + let literal = GValue::from_be_bytes(bytes)?; + match literal { + GValue::String(literal) if literal.eq_ignore_ascii_case("id") => Ok(T::Id), + GValue::String(literal) if literal.eq_ignore_ascii_case("key") => Ok(T::Key), + GValue::String(literal) if literal.eq_ignore_ascii_case("label") => Ok(T::Label), + GValue::String(literal) if literal.eq_ignore_ascii_case("value") => Ok(T::Value), + other => Err(GremlinError::Cast(format!( + "Unexpected T literal {other:?}" + ))), + } + } +} + +impl GraphBinaryV1Ser for &T { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + let literal = match self { + T::Id => "id", + T::Key => "key", + T::Label => "label", + T::Value => "value", + }; + write_fully_qualified_str(literal, buf) + } +} + +fn consume_expected_null_reference_bytes<'a, S: Iterator>( + bytes: &mut S, + null_reference_descriptor: &str, +) -> GremlinResult<()> { + let GValue::Null = GraphBinaryV1Deser::from_be_bytes(bytes)? else { + //Anything else is erroneous + return Err(GremlinError::Cast(format!( + "{null_reference_descriptor} is supposed to be a \"null\" reference" + ))); + }; + + Ok(()) +} + +impl GraphBinaryV1Deser for Edge { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: {id}{label}{inVId}{inVLabel}{outVId}{outVLabel}{parent}{properties} + + //{id} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} + let id: GValue = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{label} is a String value. + let label: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //Ideally we'd just do something like Vertex::from_be_bytes(bytes) for the in/out vertices + //however only the id & label is submitted, the "null" properties byte is not + + //{inVId} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + let in_v_id: GValue = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{inVLabel} is a String value. + let in_v_label: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{outVId} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + let out_v_id: GValue = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{outVLabel} is a String value. + let out_v_label: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{parent} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains the parent Vertex. Note that as TinkerPop currently send "references" only, this value will always be null. + consume_expected_null_reference_bytes(bytes, "Parent")?; + + //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains the properties for the edge. + let properties = match GValue::from_be_bytes(bytes)? { + GValue::Null => HashMap::new(), + GValue::List(raw_properties) => raw_properties + .into_iter() + .map(|property| { + property + .take::() + .map(|converted| (converted.label().clone(), converted)) + }) + .collect::>()?, + _ => { + return Err(GremlinError::Cast(format!( + "Edge properties should either be Null or List" + ))) + } + }; + Ok(Edge::new( + id.try_into()?, + label, + in_v_id.try_into()?, + in_v_label, + out_v_id.try_into()?, + out_v_label, + properties, + )) + } +} + +impl GraphBinaryV1Deser for Property { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: {key}{value}{parent} + + //{key} is a String value + let key: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{value} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} + let value = GValue::from_be_bytes(bytes)?; + + //{parent} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which is either an Edge or VertexProperty. + //Note that as TinkerPop currently sends "references" only this value will always be null. + consume_expected_null_reference_bytes(bytes, "Parent")?; + Ok(Property::new(key, value)) + } +} + +impl GraphBinaryV1Deser for Path { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + let labels = GValue::from_be_bytes(bytes)?; + let GValue::List(objects) = GValue::from_be_bytes(bytes)? else { + return Err(GremlinError::Cast(format!("Path objects should be a list"))); + }; + Ok(Path::new(labels, objects)) + } +} + +impl GraphBinaryV1Deser for VertexProperty { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: {id}{label}{value}{parent}{properties} + //{id} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + let id: GValue = GValue::from_be_bytes(bytes)?; + let id: GID = id.try_into()?; + //{label} is a String value. + let label: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; + + //{value} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + let value: GValue = GValue::from_be_bytes(bytes)?; + + //{parent} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains the parent Vertex. + //Note that as TinkerPop currently send "references" only, this value will always be null. + consume_expected_null_reference_bytes(bytes, "Parent vertex")?; + + //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains properties. + let _ = GValue::from_be_bytes(bytes)?; //we don't have a place for this? + Ok(VertexProperty::new(id, label, value)) + } +} + +impl GraphBinaryV1Deser for Vertex { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: {id}{label}{properties} + //{id} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value}. + let id: GValue = GraphBinaryV1Deser::from_be_bytes(bytes)?; + //{label} is a String value. + let label: String = GraphBinaryV1Deser::from_be_bytes(bytes)?; + //{properties} is a fully qualified typed value composed of {type_code}{type_info}{value_flag}{value} which contains properties. + //properties: HashMap>, + let properties: GValue = GraphBinaryV1Deser::from_be_bytes(bytes)?; + match properties { + GValue::Null => Ok(Vertex::new(id.try_into()?, label, HashMap::new())), + GValue::List(list) => { + let mut properties = HashMap::new(); + for element in list.into_iter() { + let vp: VertexProperty = element.take()?; + properties.insert(vp.label().clone(), vec![vp]); + } + Ok(Vertex::new(id.try_into()?, label, properties)) + } + other => Err(GremlinError::Cast(format!( + "Unsupported vertex property type: {other:?}" + ))), + } + } +} + +impl GraphBinaryV1Ser for &str { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: {length}{text_value} + // {length} is an Int describing the byte length of the text. Length is a positive number or zero to represent the empty string. + // {text_value} is a sequence of bytes representing the string value in UTF8 encoding. + let length: i32 = self + .len() + .try_into() + .map_err(|_| GremlinError::Cast(format!("String length exceeds i32")))?; + GraphBinaryV1Ser::to_be_bytes(length, buf)?; + buf.extend_from_slice(self.as_bytes()); + Ok(()) + } +} + +impl GraphBinaryV1Ser for &DateTime { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: An 8-byte two’s complement signed integer representing a millisecond-precision offset from the unix epoch. + GraphBinaryV1Ser::to_be_bytes(self.timestamp_millis(), buf) + } +} + +impl GraphBinaryV1Ser for f32 { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + buf.extend_from_slice(&self.to_be_bytes()); + Ok(()) + } +} + +impl GraphBinaryV1Ser for f64 { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + buf.extend_from_slice(&self.to_be_bytes()); + Ok(()) + } +} + +impl GraphBinaryV1Deser for Traverser { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + Ok(Traverser::new( + GraphBinaryV1Deser::from_be_bytes(bytes)?, + GraphBinaryV1Deser::from_be_bytes(bytes)?, + )) + } +} + +impl GraphBinaryV1Deser for Vec { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + let length = ::from_be_bytes(bytes)? + .try_into() + .map_err(|_| GremlinError::Cast(format!("list length exceeds usize")))?; + let mut list = Vec::new(); + list.reserve_exact(length); + for _ in 0..length { + list.push(T::from_be_bytes(bytes)?); + } + Ok(list) + } +} + +impl GraphBinaryV1Deser for String { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + let string_bytes_length: i32 = GraphBinaryV1Deser::from_be_bytes(bytes) + .map_err(|_| GremlinError::Cast(format!("Invalid bytes for String length")))?; + let string_bytes_length = string_bytes_length + .try_into() + .map_err(|_| GremlinError::Cast(format!("String length did not fit into usize")))?; + let string_value_bytes: Vec = bytes.take(string_bytes_length).cloned().collect(); + if string_value_bytes.len() < string_bytes_length { + return Err(GremlinError::Cast(format!( + "Missing bytes for String value. Expected {} only retrieved {}", + string_bytes_length, + string_value_bytes.len(), + ))); + } + String::from_utf8(string_value_bytes) + .map_err(|_| GremlinError::Cast(format!("Invalid bytes for String value"))) + } +} + +impl GraphBinaryV1Ser for i32 { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: 4-byte two’s complement integer + buf.extend_from_slice(&self.to_be_bytes()); + Ok(()) + } +} + +impl GraphBinaryV1Ser for i64 { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + //Format: 8-byte two’s complement integer + buf.extend_from_slice(&self.to_be_bytes()); + Ok(()) + } +} + +impl GraphBinaryV1Deser for i32 { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + bytes + .take(4) + .cloned() + .collect::>() + .try_into() + .map_err(|_| GremlinError::Cast(format!("Invalid bytes into i32"))) + .map(i32::from_be_bytes) + } +} + +impl GraphBinaryV1Deser for i64 { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + //Format: 8-byte two’s complement integer + bytes + .take(8) + .cloned() + .collect::>() + .try_into() + .map_err(|_| GremlinError::Cast(format!("Invalid bytes into i64"))) + .map(i64::from_be_bytes) + } +} + +impl GraphBinaryV1Deser for f64 { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + bytes + .take(8) + .cloned() + .collect::>() + .try_into() + .map_err(|_| GremlinError::Cast(format!("Invalid bytes into f64"))) + .map(f64::from_be_bytes) + } +} + +impl GraphBinaryV1Deser for f32 { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + bytes + .take(4) + .cloned() + .collect::>() + .try_into() + .map_err(|_| GremlinError::Cast(format!("Invalid bytes into f32"))) + .map(f32::from_be_bytes) + } +} + +impl GraphBinaryV1Ser for &Uuid { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + buf.extend_from_slice(self.as_bytes().as_slice()); + Ok(()) + } +} + +impl GraphBinaryV1Ser for &GID { + fn to_be_bytes(self, buf: &mut Vec) -> GremlinResult<()> { + match self { + GID::String(s) => s.to_gvalue().to_be_bytes(buf), + GID::Int32(i) => i.to_gvalue().to_be_bytes(buf), + GID::Int64(i) => i.to_gvalue().to_be_bytes(buf), + } + } +} + +impl GraphBinaryV1Deser for Uuid { + fn from_be_bytes<'a, S: Iterator>(bytes: &mut S) -> GremlinResult { + bytes + .take(16) + .cloned() + .collect::>() + .try_into() + .map_err(|_| GremlinError::Cast(format!("Invalid bytes into Uuid"))) + .map(Uuid::from_bytes) + } +} + +#[cfg(test)] +mod tests { + use chrono::DateTime; + use rstest::rstest; + use std::iter; + use uuid::uuid; + + use super::*; + + #[rstest] + //Non-Null i32 Integer (01 00) + #[case::int_1(&[0x01, 0x00, 0x00, 0x00, 0x00, 0x01], GValue::Int32(1))] + #[case::int_256(&[0x01, 0x00, 0x00, 0x00, 0x01, 0x00], GValue::Int32(256))] + #[case::int_257(&[0x01, 0x00, 0x00, 0x00, 0x01, 0x01], GValue::Int32(257))] + #[case::int_neg_1(&[0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFF], GValue::Int32(-1))] + #[case::int_neg_2(&[0x01, 0x00, 0xFF, 0xFF, 0xFF, 0xFE], GValue::Int32(-2))] + //Non-Null i64 Long (02 00) + #[case::long_1(&[0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01], GValue::Int64(1))] + #[case::long_neg_2(&[0x02, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFE], GValue::Int64(-2))] + //Non-Null Strings (03 00) + #[case::str_abc(&[0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63], GValue::String("abc".into()))] + #[case::str_abcd(&[0x03, 0x00, 0x00, 0x00, 0x00, 0x04, 0x61, 0x62, 0x63, 0x64], GValue::String("abcd".into()))] + #[case::empty_str(&[0x03, 0x00, 0x00, 0x00, 0x00, 0x00], GValue::String("".into()))] + //Non-Null Date (04 00) + #[case::date_epoch(&[0x04, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], GValue::Date(DateTime::parse_from_rfc3339("1970-01-01T00:00:00.000Z").unwrap().into()))] + #[case::date_before_epoch(&[0x04, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF], GValue::Date(DateTime::parse_from_rfc3339("1969-12-31T23:59:59.999Z").unwrap().into()))] + //Non-Null Timestamp (05 00), no GValue at this time + //Non-Null Class (06 00), no GValue at this time + //Non-Null Double (07 00) + #[case::double_1(&[0x07, 0x00, 0x3F, 0xF0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], GValue::Double(1f64))] + #[case::double_fractional(&[0x07, 0x00, 0x3F, 0x70, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00], GValue::Double(0.00390625))] + #[case::double_0_dot_1(&[0x07, 0x00, 0x3F, 0xB9, 0x99, 0x99, 0x99, 0x99, 0x99, 0x9A], GValue::Double(0.1))] + //Non-Null Float (08 00) + #[case::double_fractional(&[0x08, 0x00, 0x3F, 0x80, 0x00, 0x00], GValue::Float(1f32))] + #[case::double_0_dot_1(&[0x08, 0x00, 0x3E, 0xC0, 0x00, 0x00], GValue::Float(0.375f32))] + //Non-Null List (09 00) + #[case::list_single_int(&[0x09, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01], GValue::List(Vec::from([GValue::Int32(1)]).into()))] + //Non-Null Map (0A 00) + #[case::map_single_str_int_pair(&[0x0A, 0x00, 0x00, 0x00, 0x00, 0x01, 0x03, 0x00, 0x00, 0x00, 0x00, 0x03, 0x61, 0x62, 0x63, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01], GValue::Map(iter::once((GKey::String("abc".into()), GValue::Int32(1))).collect::>().into()))] + //Non-Null Set (0B 00) + #[case::set_single_int(&[0x0B, 0x00, 0x00, 0x00, 0x00, 0x01, 0x01, 0x00, 0x00, 0x00, 0x00, 0x01], GValue::Set(Vec::from([GValue::Int32(1)]).into()))] + //Non-Null UUID (0C 00) + #[case::uuid(&[0x0C, 0x00, 0x00, 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88, 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF], GValue::Uuid(uuid!("00112233-4455-6677-8899-aabbccddeeff")))] + fn serde_values(#[case] expected_serialized: &[u8], #[case] expected: GValue) { + let mut serialized = Vec::new(); + (&expected) + .to_be_bytes(&mut serialized) + .expect("Shouldn't fail parsing"); + assert_eq!(serialized, expected_serialized); + let deserialized: GValue = GraphBinaryV1Deser::from_be_bytes(&mut serialized.iter()) + .expect("Shouldn't fail parsing"); + assert_eq!(deserialized, expected); + } + + #[rstest] + #[case::too_few_bytes( &[0x01, 0x00, 0x00, 0x00, 0x00])] + fn serde_int32_invalid_bytes(#[case] bytes: &[u8]) { + ::from_be_bytes(&mut bytes.iter()) + .expect_err("Should have failed due invalid bytes"); + } +} diff --git a/gremlin-client/src/io/macros.rs b/gremlin-client/src/io/macros.rs index 3829fd70..f787df39 100644 --- a/gremlin-client/src/io/macros.rs +++ b/gremlin-client/src/io/macros.rs @@ -3,6 +3,8 @@ macro_rules! g_serializer { pub fn $name(val: &Value) -> GremlinResult { if let Value::String(ref s) = val { Ok(s.clone().into()) + } else if let Value::Null = val { + Ok(GValue::Null) } else if let Value::Bool(b) = val { Ok((*b).into()) } else { @@ -26,6 +28,8 @@ macro_rules! g_serializer_2 { pub fn $name(val: &Value) -> GremlinResult { if let Value::String(ref s) = val { return Ok(s.clone().into()) + } else if let Value::Null = val { + return Ok(GValue::Null) } if let Value::Array(_) = val { let _type = "g:List"; diff --git a/gremlin-client/src/io/mod.rs b/gremlin-client/src/io/mod.rs index 214d59ef..1a8ae4a9 100644 --- a/gremlin-client/src/io/mod.rs +++ b/gremlin-client/src/io/mod.rs @@ -1,34 +1,129 @@ #[macro_use] mod macros; +mod graph_binary_v1; mod serializer_v2; mod serializer_v3; use crate::conversion::ToGValue; +use crate::message::{ReponseStatus, RequestIdV2, Response, ResponseResult}; use crate::process::traversal::{Order, Scope}; use crate::structure::{Cardinality, Direction, GValue, Merge, T}; +use graph_binary_v1::GraphBinaryV1Deser; +use serde_derive::Deserialize; use serde_json::{json, Map, Value}; +use std::collections::HashMap; use std::string::ToString; +use uuid::Uuid; -use crate::{GremlinError, GremlinResult}; +use crate::{io::graph_binary_v1::GraphBinaryV1Ser, GremlinError, GremlinResult, Message}; -#[derive(Debug, Clone)] -pub enum GraphSON { - V2, - V3, +#[derive(Debug, Clone, PartialEq)] +pub enum IoProtocol { + GraphSONV2, + GraphSONV3, + GraphBinaryV1, } -impl GraphSON { - pub fn read(&self, value: &Value) -> GremlinResult> { - if let Value::Null = value { - return Ok(None); - } +#[derive(Debug, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct MiddleResponse { + pub request_id: Uuid, + pub result: MiddleResponseResult, + pub status: ReponseStatus, +} + +#[derive(Debug, Deserialize)] +pub struct MiddleResponseResult { + pub data: Value, +} + +impl IoProtocol { + pub fn read_response(&self, response: Vec) -> GremlinResult { match self { - GraphSON::V2 => serializer_v2::deserializer_v2(value).map(Some), - GraphSON::V3 => serializer_v3::deserializer_v3(value).map(Some), + IoProtocol::GraphSONV2 => { + let middle_form: MiddleResponse = + serde_json::from_slice(&response).map_err(GremlinError::from)?; + Ok(Response { + request_id: Some(middle_form.request_id), + result: ResponseResult { + data: serializer_v2::deserializer_v2(&middle_form.result.data).map(Some)?, + }, + status: middle_form.status, + }) + } + IoProtocol::GraphSONV3 => { + let middle_form: MiddleResponse = + serde_json::from_slice(&response).map_err(GremlinError::from)?; + Ok(Response { + request_id: Some(middle_form.request_id), + result: ResponseResult { + data: serializer_v3::deserializer_v3(&middle_form.result.data).map(Some)?, + }, + status: middle_form.status, + }) + } + IoProtocol::GraphBinaryV1 => { + graph_binary_v1::ResponseMessage::from_be_bytes(&mut response.iter()) + .map(|middle| middle.into()) + } } } - pub fn write(&self, value: &GValue) -> GremlinResult { + pub fn build_message( + &self, + op: &str, + processor: &str, + args: HashMap, + request_id: Option, + ) -> GremlinResult<(Uuid, Vec)> { + let content_type = self.content_type(); + let request_id = request_id.unwrap_or_else(Uuid::new_v4); + let message_bytes = match self { + IoProtocol::GraphSONV2 | IoProtocol::GraphSONV3 => { + let op = op.into(); + let processor = processor.into(); + let args = self.write_graphson(&GValue::from(args))?; + let message = match self { + IoProtocol::GraphSONV2 => Message::V2 { + request_id: RequestIdV2 { + id_type: "g:UUID".to_string(), + value: request_id, + }, + op, + processor, + args, + }, + IoProtocol::GraphSONV3 => Message::V3 { + request_id, + op, + processor, + args, + }, + _ => unreachable!("Invalid branch"), + }; + + let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; + let payload = String::from("") + content_type + &msg; + let mut binary = payload.into_bytes(); + binary.insert(0, content_type.len() as u8); + binary + } + IoProtocol::GraphBinaryV1 => { + let mut message_bytes: Vec = Vec::new(); + graph_binary_v1::RequestMessage { + request_id, + op, + processor, + args, + } + .to_be_bytes(&mut message_bytes)?; + message_bytes + } + }; + Ok((request_id, message_bytes)) + } + + fn write_graphson(&self, value: &GValue) -> GremlinResult { match (self, value) { (_, GValue::Double(d)) => Ok(json!({ "@type" : "g:Double", @@ -55,12 +150,14 @@ impl GraphSON { "@type" : "g:Date", "@value" : d.timestamp_millis() })), - (GraphSON::V2, GValue::List(d)) => { - let elements: GremlinResult> = d.iter().map(|e| self.write(e)).collect(); + (IoProtocol::GraphSONV2, GValue::List(d)) => { + let elements: GremlinResult> = + d.iter().map(|e| self.write_graphson(e)).collect(); Ok(json!(elements?)) } - (GraphSON::V3, GValue::List(d)) => { - let elements: GremlinResult> = d.iter().map(|e| self.write(e)).collect(); + (IoProtocol::GraphSONV3, GValue::List(d)) => { + let elements: GremlinResult> = + d.iter().map(|e| self.write_graphson(e)).collect(); Ok(json!({ "@type" : "g:List", "@value" : elements? @@ -70,7 +167,7 @@ impl GraphSON { "@type" : "g:P", "@value" : { "predicate" : p.operator(), - "value" : self.write(p.value())? + "value" : self.write_graphson(p.value())? } })), (_, GValue::Bytecode(code)) => { @@ -82,7 +179,7 @@ impl GraphSON { instruction.push(Value::String(m.operator().clone())); let arguments: GremlinResult> = - m.args().iter().map(|a| self.write(a)).collect(); + m.args().iter().map(|a| self.write_graphson(a)).collect(); instruction.extend(arguments?); Ok(Value::Array(instruction)) @@ -97,7 +194,7 @@ impl GraphSON { instruction.push(Value::String(m.operator().clone())); let arguments: GremlinResult> = - m.args().iter().map(|a| self.write(a)).collect(); + m.args().iter().map(|a| self.write_graphson(a)).collect(); instruction.extend(arguments?); Ok(Value::Array(instruction)) @@ -112,7 +209,7 @@ impl GraphSON { })) } (_, GValue::Vertex(v)) => { - let id = self.write(&v.id().to_gvalue())?; + let id = self.write_graphson(&v.id().to_gvalue())?; Ok(json!({ "@type" : "g:Vertex", "@value" : { @@ -120,29 +217,29 @@ impl GraphSON { } })) } - (GraphSON::V2, GValue::Map(map)) => { + (IoProtocol::GraphSONV2, GValue::Map(map)) => { let mut params = Map::new(); for (k, v) in map.iter() { params.insert( - self.write(&k.clone().into())? + self.write_graphson(&k.clone().into())? .as_str() .ok_or_else(|| { GremlinError::Generic("Non-string key value.".to_string()) })? .to_string(), - self.write(&v)?, + self.write_graphson(&v)?, ); } Ok(json!(params)) } - (GraphSON::V3, GValue::Map(map)) => { + (IoProtocol::GraphSONV3, GValue::Map(map)) => { let mut params = vec![]; for (k, v) in map.iter() { - params.push(self.write(&k.clone().into())?); - params.push(self.write(&v)?); + params.push(self.write_graphson(&k.clone().into())?); + params.push(self.write_graphson(&v)?); } Ok(json!({ @@ -198,7 +295,7 @@ impl GraphSON { "@type" : "g:TextP", "@value" : { "predicate" : text_p.operator(), - "value" : self.write(text_p.value())? + "value" : self.write_graphson(text_p.value())? } })), (_, GValue::Pop(pop)) => Ok(json!({ @@ -254,8 +351,9 @@ impl GraphSON { pub fn content_type(&self) -> &str { match self { - GraphSON::V2 => "application/vnd.gremlin-v2.0+json", - GraphSON::V3 => "application/vnd.gremlin-v3.0+json", + IoProtocol::GraphSONV2 => "application/vnd.gremlin-v2.0+json", + IoProtocol::GraphSONV3 => "application/vnd.gremlin-v3.0+json", + IoProtocol::GraphBinaryV1 => "application/vnd.graphbinary-v1.0", } } } diff --git a/gremlin-client/src/io/serializer_v2.rs b/gremlin-client/src/io/serializer_v2.rs index a67b2fae..1042f68c 100644 --- a/gremlin-client/src/io/serializer_v2.rs +++ b/gremlin-client/src/io/serializer_v2.rs @@ -177,6 +177,26 @@ where let out_v_id = deserialize_id(reader, &val["outV"])?; let out_v_label = get_value!(&val["outVLabel"], Value::String)?.clone(); + let json_properties = &val["properties"]; + let properties = if json_properties.is_object() { + get_value!(&json_properties, Value::Object)? + .into_iter() + .map(|(key, value)| { + deserializer_v2(value) + .map(|deserialized| { + //if the given value is a property, unfurl it + match deserialized { + GValue::Property(property) => property.value().clone(), + other => other, + } + }) + .map(|property| (key.clone(), Property::new(key, property))) + }) + .collect::>()? + } else { + HashMap::new() + }; + Ok(Edge::new( id, label, @@ -184,7 +204,7 @@ where in_v_label, out_v_id, out_v_label, - HashMap::new(), + properties, ) .into()) } @@ -431,7 +451,7 @@ mod tests { use super::deserializer_v2; use serde_json::json; - use crate::{edge, vertex}; + use crate::{edge, vertex, Edge}; use crate::structure::{GValue, Map, Path, Property, Token, Vertex, VertexProperty, GID}; use chrono::offset::TimeZone; @@ -593,29 +613,32 @@ mod tests { #[test] fn test_edge() { - let value = json!({"@type":"g:Edge","@value":{"id":{"@type":"g:Int32","@value":13},"label":"develops","inVLabel":"software","outVLabel":"person","inV":{"@type":"g:Int32","@value":10},"outV":{"@type":"g:Int32","@value":1},"properties":{"since":{"@type":"g:Property","@value":{"key":"since","value":{"@type":"g:Int32","@value":2009}}}}}}); + let value = json!({"@type":"g:Edge","@value":{"id":{"@type":"g:Int32","@value":13},"label":"develops","inVLabel":"software","outVLabel":"person","inV":{"@type":"g:Int32","@value":10},"outV":{"@type":"g:Int32","@value":1},"properties":{"since":{"@type":"g:Int32","@value":2009}}}}); let result = deserializer_v2(&value).expect("Failed to deserialize an Edge"); + let actual: Edge = result.take().expect("Should have deserialized"); + + let expected = edge!({ + id => 13, + label=> "develops", + inV => { + id => 10, + label => "software" + }, + outV => { + id => 1, + label => "person" + }, + properties => { + "since" => 2009i32 + } + }); - assert_eq!( - result, - edge!({ - id => 13, - label=> "develops", - inV => { - id => 10, - label => "software" - }, - outV => { - id => 1, - label => "person" - }, - properties => { - - } - }) - .into() - ); + assert_eq!(actual, expected); + //Now make sure the properties deserialized correctly + let expected_properties: Vec<(String, Property)> = expected.into_iter().collect(); + let actual_properites: Vec<(String, Property)> = actual.into_iter().collect(); + assert_eq!(actual_properites, expected_properties); } #[test] diff --git a/gremlin-client/src/io/serializer_v3.rs b/gremlin-client/src/io/serializer_v3.rs index ec96fc7b..ea4c6134 100644 --- a/gremlin-client/src/io/serializer_v3.rs +++ b/gremlin-client/src/io/serializer_v3.rs @@ -170,6 +170,20 @@ where let out_v_id = deserialize_id(reader, &val["outV"])?; let out_v_label = get_value!(&val["outVLabel"], Value::String)?.clone(); + let json_properties = &val["properties"]; + let properties = if json_properties.is_object() { + get_value!(&json_properties, Value::Object)? + .values() + .map(|value| { + deserialize_property(reader, &value["@value"]) + .and_then(|property| property.take::()) + .map(|property| (property.label().clone(), property)) + }) + .collect::>()? + } else { + HashMap::new() + }; + Ok(Edge::new( id, label, @@ -177,7 +191,7 @@ where in_v_label, out_v_id, out_v_label, - HashMap::new(), + properties, ) .into()) } @@ -439,7 +453,7 @@ mod tests { use super::deserializer_v3; use serde_json::json; - use crate::{edge, vertex}; + use crate::{edge, vertex, Edge}; use crate::structure::{ GValue, Map, Metric, Path, Property, Token, TraversalMetrics, Vertex, VertexProperty, GID, @@ -626,26 +640,29 @@ mod tests { let value = json!({"@type":"g:Edge","@value":{"id":{"@type":"g:Int32","@value":13},"label":"develops","inVLabel":"software","outVLabel":"person","inV":{"@type":"g:Int32","@value":10},"outV":{"@type":"g:Int32","@value":1},"properties":{"since":{"@type":"g:Property","@value":{"key":"since","value":{"@type":"g:Int32","@value":2009}}}}}}); let result = deserializer_v3(&value).expect("Failed to deserialize an Edge"); + let actual: Edge = result.take().expect("Should have deserialized"); + + let expected = edge!({ + id => 13, + label=> "develops", + inV => { + id => 10, + label => "software" + }, + outV => { + id => 1, + label => "person" + }, + properties => { + "since" => 2009i32 + } + }); - assert_eq!( - result, - edge!({ - id => 13, - label=> "develops", - inV => { - id => 10, - label => "software" - }, - outV => { - id => 1, - label => "person" - }, - properties => { - - } - }) - .into() - ); + assert_eq!(actual, expected); + //Now make sure the properties deserialized correctly + let expected_properties: Vec<(String, Property)> = expected.into_iter().collect(); + let actual_properites: Vec<(String, Property)> = actual.into_iter().collect(); + assert_eq!(actual_properites, expected_properties); } #[test] diff --git a/gremlin-client/src/lib.rs b/gremlin-client/src/lib.rs index 8b28ce22..13e7279e 100644 --- a/gremlin-client/src/lib.rs +++ b/gremlin-client/src/lib.rs @@ -128,7 +128,7 @@ pub use connection::{ }; pub use conversion::{BorrowFromGValue, FromGValue, ToGValue}; pub use error::GremlinError; -pub use io::GraphSON; +pub use io::IoProtocol; pub use message::Message; pub type GremlinResult = Result; diff --git a/gremlin-client/src/message.rs b/gremlin-client/src/message.rs index bd2530b3..39a69cc2 100644 --- a/gremlin-client/src/message.rs +++ b/gremlin-client/src/message.rs @@ -3,14 +3,16 @@ use serde_derive::{Deserialize, Serialize}; use serde_json::Value; use uuid::Uuid; +use crate::GValue; + #[derive(Serialize)] #[serde(rename_all = "camelCase")] pub struct RequestIdV2 { #[serde(rename = "@type")] - id_type: String, + pub id_type: String, #[serde(rename = "@value")] - value: Uuid, + pub value: Uuid, } #[derive(Serialize)] @@ -49,17 +51,16 @@ impl Message { } } } -#[derive(Debug, Deserialize)] -#[serde(rename_all = "camelCase")] +#[derive(Debug)] pub struct Response { - pub request_id: Uuid, + pub request_id: Option, pub result: ResponseResult, pub status: ReponseStatus, } -#[derive(Debug, Deserialize)] +#[derive(Debug)] pub struct ResponseResult { - pub data: Value, + pub data: Option, } #[derive(Debug, Deserialize)] @@ -78,45 +79,6 @@ where Option::::deserialize(de).map(Option::unwrap_or_default) } -pub fn message_with_args_v2(op: String, processor: String, args: T) -> Message { - message_with_args_and_uuid_v2(op, processor, Uuid::new_v4(), args) -} - -pub fn message_with_args_and_uuid_v2( - op: String, - processor: String, - id: Uuid, - args: T, -) -> Message { - Message::V2 { - request_id: RequestIdV2 { - id_type: "g:UUID".to_string(), - value: id, - }, - op, - processor, - args, - } -} - -pub fn message_with_args(op: String, processor: String, args: T) -> Message { - message_with_args_and_uuid(op, processor, Uuid::new_v4(), args) -} - -pub fn message_with_args_and_uuid( - op: String, - processor: String, - id: Uuid, - args: T, -) -> Message { - Message::V3 { - request_id: id, - op, - processor, - args, - } -} - #[cfg(test)] mod tests { use crate::message::ReponseStatus; diff --git a/gremlin-client/src/pool.rs b/gremlin-client/src/pool.rs index 2085f356..b0c72347 100644 --- a/gremlin-client/src/pool.rs +++ b/gremlin-client/src/pool.rs @@ -3,10 +3,8 @@ use r2d2::ManageConnection; use crate::connection::Connection; use crate::connection::ConnectionOptions; use crate::error::GremlinError; -use crate::message::{ - message_with_args, message_with_args_and_uuid, message_with_args_v2, Response, -}; -use crate::{GValue, GraphSON, GremlinResult}; +use crate::message::Response; +use crate::{GValue, GremlinResult, IoProtocol}; use base64::encode; use std::collections::HashMap; @@ -40,28 +38,15 @@ impl ManageConnection for GremlinConnectionManager { String::from("language"), GValue::String(String::from("gremlin-groovy")), ); - let args = self.options.serializer.write(&GValue::from(args))?; - let message = match self.options.serializer { - GraphSON::V2 => message_with_args_v2(String::from("eval"), String::default(), args), - GraphSON::V3 => message_with_args(String::from("eval"), String::default(), args), - }; - - let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; - - let content_type = match self.options.serializer { - GraphSON::V2 => "application/vnd.gremlin-v2.0+json", - GraphSON::V3 => "application/vnd.gremlin-v3.0+json", - }; - let payload = String::from("") + content_type + &msg; - - let mut binary = payload.into_bytes(); - binary.insert(0, content_type.len() as u8); - - conn.send(binary)?; + let (_, message) = self + .options + .serializer + .build_message("eval", "", args, None)?; + conn.send(message)?; let result = conn.recv()?; - let response: Response = serde_json::from_slice(&result)?; + let response = self.options.deserializer.read_response(result)?; match response.status.code { 200 | 206 => Ok(()), @@ -75,26 +60,20 @@ impl ManageConnection for GremlinConnectionManager { GValue::String(encode(&format!("\0{}\0{}", c.username, c.password))), ); - let args = self.options.serializer.write(&GValue::from(args))?; - let message = message_with_args_and_uuid( - String::from("authentication"), - String::from("traversal"), - response.request_id, + let (_, message) = self.options.serializer.build_message( + "authentication", + "traversal", args, - ); - - let msg = serde_json::to_string(&message).map_err(GremlinError::from)?; - - let content_type = self.options.serializer.content_type(); - let payload = String::from("") + content_type + &msg; - - let mut binary = payload.into_bytes(); - binary.insert(0, content_type.len() as u8); - - conn.send(binary)?; + Some( + response + .request_id + .expect("Auth challenge requires response id"), + ), + )?; + conn.send(message)?; let result = conn.recv()?; - let response: Response = serde_json::from_slice(&result)?; + let response = self.options.deserializer.read_response(result)?; match response.status.code { 200 | 206 => Ok(()), diff --git a/gremlin-client/src/process/traversal/anonymous_traversal_source.rs b/gremlin-client/src/process/traversal/anonymous_traversal_source.rs index 8765697a..9a13fcaf 100644 --- a/gremlin-client/src/process/traversal/anonymous_traversal_source.rs +++ b/gremlin-client/src/process/traversal/anonymous_traversal_source.rs @@ -7,8 +7,8 @@ use crate::process::traversal::step::select::SelectStep; use crate::process::traversal::step::until::UntilStep; use crate::process::traversal::step::where_step::WhereStep; use crate::process::traversal::TraversalBuilder; -use crate::structure::{Either2, GIDs, IntoPredicate, Labels, T}; -use crate::GValue; +use crate::structure::{Either2, GIDs, Labels, T}; +use crate::{GValue, ToGValue}; use super::merge_edge::MergeEdgeStep; use super::merge_vertex::MergeVertexStep; @@ -211,7 +211,7 @@ impl AnonymousTraversalSource { pub fn is(&self, val: A) -> TraversalBuilder where - A: IntoPredicate, + A: ToGValue, { self.traversal.clone().is(val) } diff --git a/gremlin-client/src/process/traversal/builder.rs b/gremlin-client/src/process/traversal/builder.rs index 49491aea..b901207b 100644 --- a/gremlin-client/src/process/traversal/builder.rs +++ b/gremlin-client/src/process/traversal/builder.rs @@ -18,7 +18,7 @@ use crate::process::traversal::step::until::UntilStep; use crate::process::traversal::step::where_step::WhereStep; use crate::process::traversal::{Bytecode, Scope}; -use crate::structure::{Cardinality, GIDs, IntoPredicate, Labels}; +use crate::structure::{Cardinality, GIDs, Labels}; use crate::GValue; use super::merge_edge::MergeEdgeStep; @@ -505,10 +505,10 @@ impl TraversalBuilder { pub fn is(mut self, val: A) -> Self where - A: IntoPredicate, + A: ToGValue, { self.bytecode - .add_step(String::from("is"), vec![val.into_predicate().into()]); + .add_step(String::from("is"), vec![val.to_gvalue()]); self } diff --git a/gremlin-client/src/process/traversal/graph_traversal.rs b/gremlin-client/src/process/traversal/graph_traversal.rs index be734946..7cbabdf3 100644 --- a/gremlin-client/src/process/traversal/graph_traversal.rs +++ b/gremlin-client/src/process/traversal/graph_traversal.rs @@ -24,8 +24,8 @@ use crate::process::traversal::strategies::{ use crate::process::traversal::{Bytecode, Scope, TraversalBuilder, WRITE_OPERATORS}; use crate::structure::{Cardinality, Labels, Null}; use crate::{ - structure::GIDs, structure::GProperty, structure::IntoPredicate, Edge, GValue, GremlinClient, - List, Map, Path, Vertex, + structure::GIDs, structure::GProperty, Edge, GValue, GremlinClient, List, Map, Path, ToGValue, + Vertex, }; use std::marker::PhantomData; @@ -537,7 +537,7 @@ impl> GraphTraversal { pub fn is(mut self, val: A) -> Self where - A: IntoPredicate, + A: ToGValue, { self.builder = self.builder.is(val); diff --git a/gremlin-client/src/process/traversal/graph_traversal_source.rs b/gremlin-client/src/process/traversal/graph_traversal_source.rs index ac3a3199..452b9f8f 100644 --- a/gremlin-client/src/process/traversal/graph_traversal_source.rs +++ b/gremlin-client/src/process/traversal/graph_traversal_source.rs @@ -221,14 +221,11 @@ mod tests { code.add_step(String::from("V"), vec![1.into()]); code.add_step( String::from("has"), - vec![ - String::from("name").into(), - P::new("eq", String::from("marko").into()).into(), - ], + vec![String::from("name").into(), String::from("marko").into()], ); code.add_step( String::from("has"), - vec![String::from("age").into(), P::new("eq", 23.into()).into()], + vec![String::from("age").into(), 23.into()], ); assert_eq!( @@ -246,7 +243,7 @@ mod tests { vec![ String::from("person").into(), String::from("name").into(), - P::new("eq", String::from("marko").into()).into(), + String::from("marko").into(), ], ); @@ -359,7 +356,7 @@ mod tests { code.add_step(String::from("V"), vec![1.into()]); code.add_step(String::from("values"), vec!["age".into()]); - code.add_step(String::from("is"), vec![P::eq(23).into()]); + code.add_step(String::from("is"), vec![23i32.into()]); assert_eq!(&code, g.v(1).values("age").is(23).bytecode()); } diff --git a/gremlin-client/src/process/traversal/mod.rs b/gremlin-client/src/process/traversal/mod.rs index 4001c2d2..e1a5d8d4 100644 --- a/gremlin-client/src/process/traversal/mod.rs +++ b/gremlin-client/src/process/traversal/mod.rs @@ -18,7 +18,7 @@ pub use order::Order; pub use remote::{traversal, SyncTerminator, Terminator}; pub use builder::TraversalBuilder; -pub use bytecode::{Bytecode, WRITE_OPERATORS}; +pub use bytecode::{Bytecode, Instruction, WRITE_OPERATORS}; pub use graph_traversal::GraphTraversal; pub use graph_traversal_source::GraphTraversalSource; pub use scope::Scope; diff --git a/gremlin-client/src/process/traversal/step/has.rs b/gremlin-client/src/process/traversal/step/has.rs index e250838b..34c8d52b 100644 --- a/gremlin-client/src/process/traversal/step/has.rs +++ b/gremlin-client/src/process/traversal/step/has.rs @@ -1,6 +1,6 @@ use crate::structure::GValue; -use crate::structure::{Either2, TextP, T}; -use crate::structure::{IntoPredicate, P}; +use crate::structure::T; +use crate::ToGValue; pub enum HasStepKey { Str(String), @@ -28,7 +28,7 @@ impl Into for &str { pub struct HasStep { label: Option, key: HasStepKey, - predicate: Option>, + predicate: Option, } impl From for Vec { @@ -55,13 +55,13 @@ impl From for Vec { impl From<(A, B)> for HasStep where A: Into, - B: IntoPredicate, + B: ToGValue, { fn from(param: (A, B)) -> Self { HasStep { label: None, key: param.0.into(), - predicate: Some(param.1.into_predicate()), + predicate: Some(param.1.to_gvalue()), } } } @@ -70,13 +70,13 @@ impl From<(A, B, C)> for HasStep where A: Into, B: Into, - C: IntoPredicate, + C: ToGValue, { fn from(param: (A, B, C)) -> Self { HasStep { label: Some(param.0.into()), key: param.1.into(), - predicate: Some(param.2.into_predicate()), + predicate: Some(param.2.to_gvalue()), } } } diff --git a/gremlin-client/src/structure/gid.rs b/gremlin-client/src/structure/gid.rs index 049ab4b5..2b699dce 100644 --- a/gremlin-client/src/structure/gid.rs +++ b/gremlin-client/src/structure/gid.rs @@ -1,6 +1,10 @@ +use std::convert::TryFrom; + use crate::{GremlinError, GremlinResult}; use uuid::Uuid; +use super::GValue; + #[derive(Debug, Clone)] pub struct GIDs(pub(crate) Vec); @@ -29,6 +33,21 @@ pub enum GID { Int64(i64), } +impl TryFrom for GID { + type Error = GremlinError; + + fn try_from(value: GValue) -> Result { + match value { + GValue::Int32(v) => Ok(GID::Int32(v)), + GValue::Int64(v) => Ok(GID::Int64(v)), + GValue::String(v) => Ok(GID::String(v)), + other => Err(GremlinError::Cast(format!( + "Invalid GValue for GID {other:?}" + ))), + } + } +} + impl GID { pub fn get<'a, T>(&'a self) -> GremlinResult<&'a T> where diff --git a/gremlin-client/src/structure/list.rs b/gremlin-client/src/structure/list.rs index e7aa7f8a..715deabd 100644 --- a/gremlin-client/src/structure/list.rs +++ b/gremlin-client/src/structure/list.rs @@ -16,6 +16,10 @@ impl List { self.0.iter() } + pub fn into_iter(self) -> std::vec::IntoIter { + self.0.into_iter() + } + pub fn len(&self) -> usize { self.0.len() } diff --git a/gremlin-client/src/structure/macros.rs b/gremlin-client/src/structure/macros.rs index 5b675765..43b557af 100644 --- a/gremlin-client/src/structure/macros.rs +++ b/gremlin-client/src/structure/macros.rs @@ -31,7 +31,7 @@ macro_rules! edge { #[allow(unused_mut)] let mut properties = ::std::collections::HashMap::::new(); $( - let p = Property::new($key.into(),$value.into()); + let p = Property::new($key,$value); properties.insert($key.into(),p); )* $crate::Edge::new($id.into(), $label, $inVId.into(),$inVLabel,$outVId.into(),$outVLabel,properties) diff --git a/gremlin-client/src/structure/map.rs b/gremlin-client/src/structure/map.rs index 10e491e0..def9523a 100644 --- a/gremlin-client/src/structure/map.rs +++ b/gremlin-client/src/structure/map.rs @@ -77,12 +77,37 @@ impl Map { self.0.iter() } + pub fn into_iter(self) -> IntoIter { + self.0.into_iter() + } + ///Returns a reference to the value corresponding to the key. pub fn get(&self, key: T) -> Option<&GValue> where T: Into, { - self.0.get(&key.into()) + let key = key.into(); + self.0 + .get(&key) + .or_else(|| self.backwards_compatability_get(key)) + } + + fn backwards_compatability_get(&self, key: GKey) -> Option<&GValue> { + //The GraphBinary protocol may be returning properties + //as the distinct "T" type (T::Id) whereas older protocols + //would have just sent them as GKey::String("id"), etc + //This function is intended as a fallback for backwards compatability that if a caller + //requests a get or try_get for "id" that we also then attempt it T::Id or one of its siblings + //This also maintains the representation needed for the derive crate to find + //the types since the derive crate can't discriminate between a vertex property called "id" + //or a T::Id since they're both just called "id" + match key { + GKey::String(string) => match TryInto::::try_into(string.as_str()) { + Ok(fallback_key) => self.0.get(&fallback_key.into()), + Err(_) => None, + }, + _ => None, + } } ///Returns try_get and conversion @@ -91,8 +116,10 @@ impl Map { K: Into, V: std::convert::TryFrom, { + let key = key.into(); self.0 - .get(&key.into()) + .get(&key) + .or_else(|| self.backwards_compatability_get(key)) .cloned() .or_else(|| Some(GValue::Null)) .map(V::try_from) @@ -145,6 +172,8 @@ pub enum GKey { Vertex(Vertex), Edge(Edge), Direction(Direction), + Int64(i64), + Int32(i32), } impl From for GKey { @@ -153,6 +182,18 @@ impl From for GKey { } } +impl From for GKey { + fn from(value: i64) -> Self { + GKey::Int64(value) + } +} + +impl From for GKey { + fn from(value: i32) -> Self { + GKey::Int32(value) + } +} + impl From for GKey { fn from(value: Direction) -> Self { GKey::Direction(value) diff --git a/gremlin-client/src/structure/set.rs b/gremlin-client/src/structure/set.rs index b0235ba9..dd0d9a18 100644 --- a/gremlin-client/src/structure/set.rs +++ b/gremlin-client/src/structure/set.rs @@ -14,6 +14,10 @@ impl Set { pub fn iter(&self) -> impl Iterator { self.0.iter() } + + pub fn len(&self) -> usize { + self.0.len() + } } impl Into for Vec { diff --git a/gremlin-client/src/structure/t.rs b/gremlin-client/src/structure/t.rs index 2a62492c..39900c1b 100644 --- a/gremlin-client/src/structure/t.rs +++ b/gremlin-client/src/structure/t.rs @@ -1,3 +1,5 @@ +use std::convert::TryFrom; + #[derive(Debug, PartialEq, Clone, Eq, Hash)] pub enum T { Id, @@ -5,3 +7,17 @@ pub enum T { Label, Value, } + +impl TryFrom<&str> for T { + type Error = String; + + fn try_from(value: &str) -> Result { + match value { + "id" => Ok(T::Id), + "key" => Ok(T::Key), + "label" => Ok(T::Label), + "value" => Ok(T::Value), + other => Err(format!("Unknown T literal {other:?}")), + } + } +} diff --git a/gremlin-client/src/structure/value.rs b/gremlin-client/src/structure/value.rs index 5ad9b4d5..c611be7b 100644 --- a/gremlin-client/src/structure/value.rs +++ b/gremlin-client/src/structure/value.rs @@ -266,6 +266,8 @@ impl From for GValue { GKey::Token(s) => GValue::String(s.value().clone()), GKey::Vertex(v) => GValue::Vertex(v), GKey::Edge(v) => GValue::Edge(v), + GKey::Int64(v) => GValue::Int64(v), + GKey::Int32(v) => GValue::Int32(v), } } } diff --git a/gremlin-client/src/structure/vertex.rs b/gremlin-client/src/structure/vertex.rs index fdd2a4df..b0001579 100644 --- a/gremlin-client/src/structure/vertex.rs +++ b/gremlin-client/src/structure/vertex.rs @@ -8,7 +8,7 @@ use std::hash::Hasher; pub struct Vertex { id: GID, label: String, - properties: HashMap>, + pub(crate) properties: HashMap>, } impl Vertex { diff --git a/gremlin-client/tests/common.rs b/gremlin-client/tests/common.rs index af3a5b44..dd7e76b3 100644 --- a/gremlin-client/tests/common.rs +++ b/gremlin-client/tests/common.rs @@ -1,4 +1,7 @@ -use gremlin_client::Map; +use std::convert::TryInto; + +use gremlin_client::{structure::T, Map}; +use rstest_reuse::template; pub fn assert_map_property(element_map: &Map, expected_key: &str, expected_value: &str) { let actual_prop_value: &String = element_map @@ -9,22 +12,39 @@ pub fn assert_map_property(element_map: &Map, expected_key: &str, expected_value assert_eq!(expected_value, actual_prop_value); } +#[template] +#[rstest] +#[case::graphson_v2(IoProtocol::GraphSONV2)] +#[case::graphson_v3(IoProtocol::GraphSONV3)] +#[case::graph_binary_v1(IoProtocol::GraphBinaryV1)] +fn serializers(#[case] protocol: IoProtocol) {} + #[allow(dead_code)] pub mod io { - use gremlin_client::{ConnectionOptions, Edge, GraphSON, GremlinClient, GremlinResult, Vertex}; + use gremlin_client::{ + ConnectionOptions, Edge, GremlinClient, GremlinResult, IoProtocol, Vertex, + }; pub fn connect() -> GremlinResult { GremlinClient::connect(("localhost", 8182)) } - fn connect_janusgraph_client() -> GremlinResult { - GremlinClient::connect(("localhost", 8184)) + fn connect_janusgraph_client(serializer: IoProtocol) -> GremlinResult { + GremlinClient::connect( + ConnectionOptions::builder() + .host("localhost") + .port(8184) + .serializer(serializer.clone()) + .deserializer(serializer) + .build(), + ) } - pub fn connect_serializer(serializer: GraphSON) -> GremlinResult { + pub fn connect_serializer(serializer: IoProtocol) -> GremlinResult { let port = match serializer { - GraphSON::V2 => 8182, - GraphSON::V3 => 8182, + IoProtocol::GraphSONV2 => 8182, + IoProtocol::GraphSONV3 => 8182, + IoProtocol::GraphBinaryV1 => 8182, }; GremlinClient::connect( ConnectionOptions::builder() @@ -40,21 +60,15 @@ pub mod io { connect().expect("It should connect") } - pub fn expect_janusgraph_client() -> GremlinClient { - connect_janusgraph_client().expect("It should connect") + pub fn expect_janusgraph_client(serializer: IoProtocol) -> GremlinClient { + connect_janusgraph_client(serializer).expect("It should connect") } - pub fn expect_client_serializer(serializer: GraphSON) -> GremlinClient { + pub fn expect_client_serializer(serializer: IoProtocol) -> GremlinClient { connect_serializer(serializer).expect("It should connect") } - pub fn graph() -> GremlinClient { - let client = expect_client(); - - client - } - - pub fn graph_serializer(serializer: GraphSON) -> GremlinClient { + pub fn graph_serializer(serializer: IoProtocol) -> GremlinClient { let client = expect_client_serializer(serializer); client @@ -112,7 +126,7 @@ pub mod io { pub mod aio { use gremlin_client::aio::GremlinClient; - use gremlin_client::{ConnectionOptions, Edge, GraphSON, GremlinResult, Vertex}; + use gremlin_client::{ConnectionOptions, Edge, GremlinResult, IoProtocol, Vertex}; #[cfg(feature = "async-std-runtime")] use async_std::prelude::*; @@ -126,10 +140,11 @@ pub mod aio { .expect("It should connect") } - pub async fn connect_serializer(serializer: GraphSON) -> GremlinClient { + pub async fn connect_serializer(serializer: IoProtocol) -> GremlinClient { let port = match serializer { - GraphSON::V2 => 8182, - GraphSON::V3 => 8182, + IoProtocol::GraphSONV2 => 8182, + IoProtocol::GraphSONV3 => 8182, + IoProtocol::GraphBinaryV1 => 8182, }; GremlinClient::connect( ConnectionOptions::builder() diff --git a/gremlin-client/tests/custom_vertex_ids.rs b/gremlin-client/tests/custom_vertex_ids.rs index 97b74f96..1df9028f 100644 --- a/gremlin-client/tests/custom_vertex_ids.rs +++ b/gremlin-client/tests/custom_vertex_ids.rs @@ -1,26 +1,146 @@ use std::collections::HashMap; -use common::io::{drop_vertices, expect_janusgraph_client}; +use common::io::expect_janusgraph_client; use gremlin_client::{ process::traversal::{traversal, __}, + structure::Edge, structure::T, - GKey, GValue, + GKey, GValue, IoProtocol, Property, }; +use rstest::*; +use rstest_reuse::apply; +use serial_test::serial; +use uuid::Uuid; + mod common; //Custom vertex ids are a feature offered by JanusGraph //https://docs.janusgraph.org/advanced-topics/custom-vertex-id/ -#[test] -fn test_merge_v_custom_id() { - let client = expect_janusgraph_client(); - let expected_label = "test_merge_v_custom_id"; - drop_vertices(&client, expected_label).expect("Failed to drop vertices"); +#[apply(common::serializers)] +#[serial(jg_throttle)] +#[cfg(feature = "derive")] +fn test_mapping_custom_vertex_id(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = expect_janusgraph_client(protocol); + use chrono::{DateTime, TimeZone, Utc}; + use gremlin_client::derive::FromGMap; + use gremlin_client::process::traversal::{Bytecode, TraversalBuilder}; + use std::convert::TryFrom; + + let g = traversal().with_remote(client); + + let uuid = uuid::Uuid::new_v4(); + let mark_id = create_novel_vertex_id(); + let mark = g + .add_v("test_mapping_custom_vertex_id") + .property(T::Id, mark_id.as_str()) + .property("name", "Mark") + .property("age", 22) + .property("time", 22 as i64) + .property("score", 3.2) + .property("uuid", uuid.clone()) + .property("datetime", chrono::Utc.timestamp(1551825863, 0)) + .property("date", 1551825863 as i64) + .value_map(true) + .by(TraversalBuilder::new(Bytecode::new()).unfold()) + .next() + .expect("Should get a response"); + + #[derive(Debug, PartialEq, FromGMap)] + struct Person { + id: String, + label: String, + name: String, + age: i32, + time: i64, + datetime: DateTime, + uuid: uuid::Uuid, + optional: Option, + } + let person = Person::try_from(mark.unwrap()).expect("Should get person"); + + assert_eq!( + Person { + id: mark_id, + label: String::from("test_mapping_custom_vertex_id"), + name: String::from("Mark"), + age: 22, + time: 22, + datetime: chrono::Utc.timestamp(1551825863, 0), + uuid: uuid, + optional: None + }, + person + ); +} + +#[apply(common::serializers)] +#[serial(jg_throttle)] +fn test_jg_add_e(protocol: IoProtocol) { + //It seems JanusGraph differs from the standard Tinkerpop Gremlin Server in how it returns edges + //JG returns edge properties in the same call if the edge is the terminal type + let client = expect_janusgraph_client(protocol); + let g = traversal().with_remote(client.clone()); + let v1 = g + .add_v("test_jg_add_e") + .property(T::Id, create_novel_vertex_id()) + .property("name", "foo") + .next() + .expect("Should to get response") + .expect("Should have gotten a vertex"); + + let v2 = g + .add_v("test_jg_add_e") + .property(T::Id, create_novel_vertex_id()) + .next() + .expect("Should get response") + .expect("Should have gotten a vertex"); + + let created_edge = g + .v(v1.id().clone()) + .out_e("knows") + .where_(__.in_v().id().is(v2.id().clone())) + .fold() + .coalesce::([ + __.unfold(), + __.add_e("knows") + .from(__.v(v1.id().clone())) + .to(__.v(v2.id().clone())), + ]) + .property("someKey", "someValue") + .next() + .expect("Should get response") + .expect("Should get edge"); + let actual_property = created_edge + .property("someKey") + .expect("Should have had property"); + let actual_property = actual_property + .get::() + .expect("Should have had property with expected type"); + assert_eq!(actual_property, "someValue"); +} + +#[apply(common::serializers)] +#[serial(jg_throttle)] +fn test_merge_v_custom_id(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + + let client = expect_janusgraph_client(protocol); let g = traversal().with_remote(client); - let expected_id = "test_merge_v_custom_id"; + let expected_id = create_novel_vertex_id(); + let expected_label = "test_merge_v_custom_id"; let mut start_step_map: HashMap = HashMap::new(); - start_step_map.insert(T::Id.into(), expected_id.into()); + start_step_map.insert(T::Id.into(), expected_id.clone().into()); start_step_map.insert(T::Label.into(), expected_label.into()); let actual_vertex = g .merge_v(start_step_map) @@ -28,19 +148,19 @@ fn test_merge_v_custom_id() { .expect("Should get a response") .expect("Should return a vertex"); match actual_vertex.id() { - gremlin_client::GID::String(actual) => assert_eq!(expected_id, actual), + gremlin_client::GID::String(actual) => assert_eq!(&expected_id, actual), other => panic!("Didn't get expected id type {:?}", other), } assert_eq!(expected_label, actual_vertex.label()); //Now try it as a mid-traversal step (inject is the start step) - let expected_id = "foo"; + let expected_id = create_novel_vertex_id(); let expected_property = "propValue"; let mut map_to_inject: HashMap = HashMap::new(); let mut lookup_map: HashMap = HashMap::new(); - lookup_map.insert(T::Id.into(), expected_id.into()); + lookup_map.insert(T::Id.into(), expected_id.clone().into()); lookup_map.insert(T::Label.into(), "myvertexlabel".into()); let mut property_map: HashMap = HashMap::new(); property_map.insert("propertyKey".into(), expected_property.into()); @@ -63,7 +183,7 @@ fn test_merge_v_custom_id() { .expect("Should have returned a vertex"); match actual_vertex.id() { - gremlin_client::GID::String(actual) => assert_eq!(expected_id, actual), + gremlin_client::GID::String(actual) => assert_eq!(&expected_id, actual), other => panic!("Didn't get expected id type {:?}", other), } @@ -75,21 +195,27 @@ fn test_merge_v_custom_id() { assert_eq!(expected_property, actual_property); } -#[test] -fn test_add_v_custom_id() { - let client = expect_janusgraph_client(); - let expected_id = "test_add_v_custom_id"; - let test_vertex_label = "test_add_v_custom_id"; - drop_vertices(&client, test_vertex_label).expect("Failed to drop vertices"); +#[apply(common::serializers)] +#[serial(jg_throttle)] +fn test_add_v_custom_id(protocol: IoProtocol) { + let client = expect_janusgraph_client(protocol); + let expected_id = create_novel_vertex_id(); let g = traversal().with_remote(client); let actual_vertex = g - .add_v(test_vertex_label) - .property(T::Id, expected_id) + .add_v("test_add_v_custom_id") + .property(T::Id, &expected_id) .next() .expect("Should get a response") .expect("Should return a vertex"); match actual_vertex.id() { - gremlin_client::GID::String(actual) => assert_eq!(expected_id, actual), + gremlin_client::GID::String(actual) => assert_eq!(&expected_id, actual), other => panic!("Didn't get expected id type {:?}", other), } } + +fn create_novel_vertex_id() -> String { + //JanusGraph by default treats "-" as a reserved character + //we can override it, but to stay closer to the default nature of JG just map the character to "_" + //in our generated vertex ids + Uuid::new_v4().to_string().replace("-", "_") +} diff --git a/gremlin-client/tests/graph_binary_read_write_cycle.rs b/gremlin-client/tests/graph_binary_read_write_cycle.rs new file mode 100644 index 00000000..667e45e9 --- /dev/null +++ b/gremlin-client/tests/graph_binary_read_write_cycle.rs @@ -0,0 +1,37 @@ +use std::array::IntoIter; +use std::collections::HashMap; + +use chrono::{TimeZone, Utc}; +use common::io::graph_serializer; +use gremlin_client::{ + process::traversal::{traversal, GraphTraversalSource, SyncTerminator}, + GValue, IoProtocol, +}; +use rstest::rstest; +use std::iter::FromIterator; +use uuid::Uuid; + +mod common; + +fn get_graph_source() -> GraphTraversalSource { + traversal().with_remote(graph_serializer(IoProtocol::GraphBinaryV1)) +} + +#[rstest] +#[case::int(1i32)] +#[case::long(1i64)] +#[case::string("abc")] +#[case::date(Utc.timestamp_millis(9001))] +#[case::double(0.1f64)] +#[case::float(0.1f32)] +#[case::int_list(Vec::from_iter([GValue::Int64(2)]))] +#[case::map_str_int(HashMap::<_, _>::from_iter(IntoIter::new([(String::from("abc"), GValue::Int32(1))])))] +#[case::int_set(GValue::Set(Vec::from_iter([GValue::Int64(2)]).into()))] +#[case::uuid(Uuid::new_v4())] +fn simple_value_rw_cycle>(#[case] payload: T) { + let payload = payload.into(); + assert_eq!( + get_graph_source().inject(payload.clone()).next().unwrap(), + Some(payload) + ) +} diff --git a/gremlin-client/tests/integration_client.rs b/gremlin-client/tests/integration_client.rs index 8232f4c0..ef2ffde8 100644 --- a/gremlin-client/tests/integration_client.rs +++ b/gremlin-client/tests/integration_client.rs @@ -6,32 +6,31 @@ use std::iter::FromIterator; use chrono::{offset::TimeZone, DateTime, Utc}; use gremlin_client::{ ConnectionOptions, GremlinClient, GremlinError, List, TlsOptions, ToGValue, - TraversalExplanation, TraversalMetrics, VertexProperty, + TraversalExplanation, TraversalMetrics, VertexProperty, GID, }; -use gremlin_client::{Edge, GValue, Map, Vertex}; +use gremlin_client::{Edge, GValue, IoProtocol, Map, Vertex}; -use common::io::{create_edge, create_vertex, expect_client, graph}; +use common::io::{create_edge, create_vertex, graph_serializer}; +use rstest::*; +use rstest_reuse::apply; +use serial_test::serial; -#[test] -fn test_client_connection_ok() { - expect_client(); -} - -#[test] -fn test_empty_query() { +#[apply(common::serializers)] +fn test_empty_query(protocol: IoProtocol) { + let client = graph_serializer(protocol); assert_eq!( 0, - graph() + client .execute("g.V().hasLabel('NotFound')", &[]) .expect("It should execute a traversal") .count() ) } -#[test] -fn test_session_empty_query() { - let mut graph = graph(); - let mut sessioned_graph = graph +#[apply(common::serializers)] +#[serial(test_session_empty_query)] +fn test_session_empty_query(protocol: IoProtocol) { + let mut sessioned_graph = graph_serializer(protocol) .create_session("test-session".to_string()) .expect("It should create a session."); assert_eq!( @@ -46,8 +45,8 @@ fn test_session_empty_query() { .expect("It should close the session."); } -#[test] -fn test_ok_credentials() { +#[apply(common::serializers)] +fn test_ok_credentials(#[case] protocol: IoProtocol) { let client = GremlinClient::connect( ConnectionOptions::builder() .host("localhost") @@ -57,6 +56,8 @@ fn test_ok_credentials() { .tls_options(TlsOptions { accept_invalid_certs: true, }) + .serializer(protocol.clone()) + .deserializer(protocol) .build(), ) .expect("Cannot connect"); @@ -65,8 +66,8 @@ fn test_ok_credentials() { assert!(result.is_ok(), "{:?}", result); } -#[test] -fn test_ko_credentials() { +#[apply(common::serializers)] +fn test_ko_credentials(#[case] protocol: IoProtocol) { let client = GremlinClient::connect( ConnectionOptions::builder() .host("localhost") @@ -76,6 +77,8 @@ fn test_ko_credentials() { .tls_options(TlsOptions { accept_invalid_certs: true, }) + .serializer(protocol.clone()) + .deserializer(protocol) .build(), ) .expect("Cannot connect"); @@ -84,9 +87,9 @@ fn test_ko_credentials() { assert!(result.is_err(), "{:?}", result); } -#[test] -fn test_wrong_query() { - let error = graph() +#[apply(common::serializers)] +fn test_wrong_query(protocol: IoProtocol) { + let error = graph_serializer(protocol) .execute("g.V", &[]) .expect_err("it should return an error"); @@ -99,9 +102,9 @@ fn test_wrong_query() { } } -#[test] -fn test_wrong_alias() { - let error = graph() +#[apply(common::serializers)] +fn test_wrong_alias(protocol: IoProtocol) { + let error = graph_serializer(protocol) .alias("foo") .execute("g.V()", &[]) .expect_err("it should return an error"); @@ -115,11 +118,9 @@ fn test_wrong_alias() { } } -#[test] - -fn test_vertex_query() { - let graph = graph(); - let vertices = graph +#[apply(common::serializers)] +fn test_vertex_query(protocol: IoProtocol) { + let vertices = graph_serializer(protocol) .execute( "g.V().hasLabel('person').has('name',name)", &[("name", &"marko")], @@ -132,10 +133,10 @@ fn test_vertex_query() { assert_eq!("person", vertices[0].label()); } -#[test] -fn test_edge_query() { - let graph = graph(); - let edges = graph + +#[apply(common::serializers)] +fn test_edge_query(protocol: IoProtocol) { + let edges = graph_serializer(protocol) .execute("g.E().hasLabel('knows').limit(1)", &[]) .expect("it should execute a query") .filter_map(Result::ok) @@ -146,14 +147,14 @@ fn test_edge_query() { assert_eq!("knows", edges[0].label()); } -#[test] -fn test_vertex_creation() { - let graph = graph(); - let mark = create_vertex(&graph, "mark"); +#[apply(common::serializers)] +fn test_vertex_creation(protocol: IoProtocol) { + let client = graph_serializer(protocol); + let mark = create_vertex(&client, "mark"); assert_eq!("person", mark.label()); - let value_map = graph + let value_map = client .execute("g.V(identity).valueMap()", &[("identity", mark.id())]) .expect("should fetch valueMap with properties") .filter_map(Result::ok) @@ -169,10 +170,9 @@ fn test_vertex_creation() { ); } -#[test] -fn test_complex_vertex_creation_with_option_none_properties() { - let graph = graph(); - let properties = graph +#[apply(common::serializers)] +fn test_complex_vertex_creation_with_option_none_properties(protocol: IoProtocol) { + let properties = graph_serializer(protocol) .execute(r#"g.addV('person').valueMap()"#, &[]) .expect("it should execute addV") .filter_map(Result::ok) @@ -216,9 +216,12 @@ fn test_complex_vertex_creation_with_option_none_properties() { .is_none()); } -#[test] -fn test_complex_vertex_creation_with_option_some_properties() { - let graph = graph(); +#[apply(common::serializers)] +fn test_complex_vertex_creation_with_option_some_properties(protocol: IoProtocol) { + //GraphSON V2 doesn't have maps, so skip it + if protocol == IoProtocol::GraphSONV2 { + return; + } let q = r#" g.addV('person') .property('name',name) @@ -242,7 +245,7 @@ fn test_complex_vertex_creation_with_option_some_properties() { ("uuid", &uuid), ("date", &now), ]; - let properties = graph + let properties = graph_serializer(protocol) .execute(q, params) .expect("it should execute addV") .filter_map(Result::ok) @@ -313,10 +316,8 @@ fn test_complex_vertex_creation_with_option_some_properties() { ); } -#[test] -fn test_complex_vertex_creation_with_properties() { - let graph = graph(); - +#[apply(common::serializers)] +fn test_complex_vertex_creation_with_properties(protocol: IoProtocol) { let q = r#" g.addV('person') .property('id',UUID.randomUUID()) @@ -339,7 +340,7 @@ fn test_complex_vertex_creation_with_properties() { ("dateTime", &chrono::Utc.timestamp(1551825863, 0)), ("date", &(1551825863 as i64)), ]; - let results = graph + let results = graph_serializer(protocol) .execute(q, params) .expect("it should execute addV") .filter_map(Result::ok) @@ -421,20 +422,18 @@ fn test_complex_vertex_creation_with_properties() { ); } -#[test] -fn test_inserting_date_with_milisecond_precision() { +#[apply(common::serializers)] +fn test_inserting_date_with_milisecond_precision(protocol: IoProtocol) { use chrono::offset::TimeZone; use chrono::DateTime; use chrono::Utc; - let graph = graph(); - let q = r#"g.addV('person').property('dateTime',dateTime).propertyMap()"#; let expected = chrono::Utc.timestamp(1551825863, 0); let params: &[(&str, &dyn ToGValue)] = &[("dateTime", &expected)]; - let results = graph + let results = graph_serializer(protocol) .execute(q, params) .expect("it should execute addV") .filter_map(Result::ok) @@ -456,10 +455,13 @@ fn test_inserting_date_with_milisecond_precision() { ); } -#[test] -fn test_list_cardinality() { - let graph = graph(); - +#[apply(common::serializers)] +fn test_list_cardinality(protocol: IoProtocol) { + //GraphSON V2 doesn't have lists, so skip it + if protocol == IoProtocol::GraphSONV2 { + return; + } + let client = graph_serializer(protocol); //split into 2 queries due to the bindings limit let q1 = r#" @@ -545,7 +547,7 @@ fn test_list_cardinality() { ("bool_4", &true), ]; - let results1 = graph + let results1 = client .execute(q1, params1) .expect("it should execute addV") .filter_map(Result::ok) @@ -566,7 +568,7 @@ fn test_list_cardinality() { let f32_list = properties1["float1"].clone().take::>().unwrap(); assert_eq!(f32_list, vec![1.1, 1.1, 2.3, 3.4]); - let results2 = graph + let results2 = client .execute(q2, params2) .expect("it should execute addV") .filter_map(Result::ok) @@ -595,10 +597,13 @@ fn test_list_cardinality() { assert_eq!(boolean_list, vec![false, true, false, true]); } -#[test] -fn test_set_cardinality() { - let graph = graph(); - +#[apply(common::serializers)] +fn test_set_cardinality(protocol: IoProtocol) { + //GraphSON V2 doesn't have sets, so skip it + if protocol == IoProtocol::GraphSONV2 { + return; + } + let client = graph_serializer(protocol); //split into 2 queries due to the bindings limit let q1 = r#" @@ -667,7 +672,7 @@ fn test_set_cardinality() { ("bool_4", &true), ]; - let results1 = graph + let results1 = client .execute(q1, params1) .expect("it should execute addV") .filter_map(Result::ok) @@ -705,7 +710,7 @@ fn test_set_cardinality() { .unwrap(); assert_eq!(i64_set, HashSet::from_iter(vec![4, 5, 6].iter().cloned())); - let results2 = graph + let results2 = client .execute(q2, params2) .expect("it should execute addV") .filter_map(Result::ok) @@ -743,20 +748,20 @@ fn test_set_cardinality() { ); } -#[test] -fn test_edge_creation() { - let graph = graph(); - let mark = create_vertex(&graph, "mark"); - let frank = create_vertex(&graph, "frank"); +#[apply(common::serializers)] +fn test_edge_creation(protocol: IoProtocol) { + let client = graph_serializer(protocol); + let mark = create_vertex(&client, "mark"); + let frank = create_vertex(&client, "frank"); - let edge = create_edge(&graph, &mark, &frank, "knows"); + let edge = create_edge(&client, &mark, &frank, "knows"); assert_eq!("knows", edge.label()); assert_eq!(&mark, edge.out_v()); assert_eq!(&frank, edge.in_v()); - let edges = graph + let edges = client .execute("g.V(identity).outE()", &[("identity", mark.id())]) .expect("should fetch edge") .filter_map(Result::ok) @@ -774,11 +779,9 @@ fn test_edge_creation() { assert_eq!(&frank, edge.in_v()); } -#[test] -fn test_profile() { - let graph = graph(); - - let metrics = graph +#[apply(common::serializers)] +fn test_profile(protocol: IoProtocol) { + let metrics = graph_serializer(protocol) .execute("g.V().limit(1).profile()", &[]) .expect("should return a profile") .filter_map(Result::ok) @@ -806,11 +809,9 @@ fn test_profile() { ); } -#[test] -fn test_explain() { - let graph = graph(); - - let metrics = graph +#[apply(common::serializers)] +fn test_explain(protocol: IoProtocol) { + let metrics = graph_serializer(protocol) .execute("g.V().limit(1).explain()", &[]) .expect("should return a profile") .filter_map(Result::ok) @@ -840,16 +841,15 @@ fn test_explain() { ); } -#[test] - -fn test_group_count_vertex() { - let graph = graph(); - let mark = create_vertex(&graph, "mark"); - let frank = create_vertex(&graph, "frank"); +#[apply(common::serializers)] +fn test_group_count_vertex(protocol: IoProtocol) { + let client = graph_serializer(protocol.clone()); + let mark = create_vertex(&client, "mark"); + let frank = create_vertex(&client, "frank"); - create_edge(&graph, &mark, &frank, "knows"); + create_edge(&client, &mark, &frank, "knows"); - let map = graph + let map = client .execute( "g.V(identity).out().groupCount()", &[("identity", mark.id())], @@ -866,21 +866,31 @@ fn test_group_count_vertex() { assert_eq!(1, first.len()); - let count = first.get(&frank); + let count = if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 just sends a simplified map, + //so we need to look up by the edge's id as a simplified type + //instead of as a GID + first.get(match frank.id() { + GID::String(s) => s.to_string(), + GID::Int32(i) => i.to_string(), + GID::Int64(i) => i.to_string(), + }) + } else { + first.get(&frank) + }; assert_eq!(Some(&GValue::Int64(1)), count); } -#[test] - -fn test_group_count_edge() { - let graph = graph(); - let mark = create_vertex(&graph, "mark"); - let frank = create_vertex(&graph, "frank"); +#[apply(common::serializers)] +fn test_group_count_edge(protocol: IoProtocol) { + let client = graph_serializer(protocol.clone()); + let mark = create_vertex(&client, "mark"); + let frank = create_vertex(&client, "frank"); - let edge = create_edge(&graph, &mark, &frank, "knows"); + let edge = create_edge(&client, &mark, &frank, "knows"); - let map = graph + let map = client .execute( "g.V(identity).outE().groupCount()", &[("identity", mark.id())], @@ -897,17 +907,28 @@ fn test_group_count_edge() { assert_eq!(1, first.len()); - let count = first.get(&edge); + let count = if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 just sends a simplified map, + //so we need to look up by the edge's id as a simplified type + //instead of as a GID + first.get(match edge.id() { + GID::String(s) => s.to_string(), + GID::Int32(i) => i.to_string(), + GID::Int64(i) => i.to_string(), + }) + } else { + first.get(&edge) + }; assert_eq!(Some(&GValue::Int64(1)), count); } -#[test] +#[apply(common::serializers)] #[cfg(feature = "derive")] -fn test_vertex_mapping() { - let graph = graph(); +fn test_vertex_mapping(protocol: IoProtocol) { use gremlin_client::derive::FromGValue; use std::convert::TryFrom; + let client = graph_serializer(protocol.clone()); let q = r#" g.addV('person') @@ -930,7 +951,7 @@ fn test_vertex_mapping() { ("dateTime", &chrono::Utc.timestamp(1551825863, 0)), ("date", &(1551825863 as i64)), ]; - let mark = graph + let mark = client .execute(q, params) .expect("should create a vertex") .filter_map(Result::ok) @@ -948,7 +969,7 @@ fn test_vertex_mapping() { assert_eq!("person", mark[0].label()); - let value_map = graph + let value_map = client .execute("g.V(identity).valueMap()", &[("identity", mark[0].id())]) .expect("should fetch valueMap with properties") .filter_map(Result::ok) diff --git a/gremlin-client/tests/integration_client_async.rs b/gremlin-client/tests/integration_client_async.rs index c936cc68..4f74263f 100644 --- a/gremlin-client/tests/integration_client_async.rs +++ b/gremlin-client/tests/integration_client_async.rs @@ -5,24 +5,27 @@ mod common; mod aio { use gremlin_client::{aio::GremlinClient, ConnectionOptions, GremlinError, TlsOptions}; - use gremlin_client::{Edge, GValue, Map, Vertex}; + use gremlin_client::{Edge, GValue, IoProtocol, Map, Vertex}; - use super::common::aio::{connect, create_edge, create_vertex, drop_vertices}; + use rstest::*; + use rstest_reuse::{self, *}; + use serial_test::serial; + + use crate::common; + + use super::common::aio::{ + connect, connect_serializer, create_edge, create_vertex, drop_vertices, + }; #[cfg(feature = "async-std-runtime")] use async_std::prelude::*; #[cfg(feature = "tokio-runtime")] use tokio_stream::StreamExt; + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_client_connection_ok() { - connect().await; - } - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_ok_credentials() { + async fn test_ok_credentials(protocol: IoProtocol) { let client = GremlinClient::connect( ConnectionOptions::builder() .host("localhost") @@ -32,6 +35,8 @@ mod aio { .tls_options(TlsOptions { accept_invalid_certs: true, }) + .serializer(protocol.clone()) + .deserializer(protocol) .build(), ) .await @@ -41,10 +46,11 @@ mod aio { assert!(result.is_ok(), "{:?}", result); } + #[apply(common::serializers)] #[cfg(feature = "async-std-runtime")] #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn test_empty_query() { - let graph = connect().await; + async fn test_empty_query(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; assert_eq!( 0, @@ -57,10 +63,12 @@ mod aio { ) } + #[apply(common::serializers)] + #[serial(test_session_empty_query)] #[cfg(feature = "async-std-runtime")] #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn test_session_empty_query() { - let mut graph = connect().await; + async fn test_session_empty_query(protocol: IoProtocol) { + let mut graph = connect_serializer(protocol).await; let mut sessioned_graph = graph .create_session("test-session".to_string()) .await @@ -82,10 +90,11 @@ mod aio { .expect("It should close the session."); } + #[apply(common::serializers)] #[cfg(feature = "async-std-runtime")] #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn test_keep_alive_query() { - let graph = connect().await; + async fn test_keep_alive_query(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; assert_eq!( 0, @@ -110,10 +119,12 @@ mod aio { ) } + #[apply(common::serializers)] + #[serial(test_partial_content)] #[cfg(feature = "async-std-runtime")] #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn test_partial_content() { - let graph = connect().await; + async fn test_partial_content(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; drop_vertices(&graph, "Partial") .await @@ -137,10 +148,11 @@ mod aio { ); } + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_wrong_query() { - let error = connect() + async fn test_wrong_query(protocol: IoProtocol) { + let error = connect_serializer(protocol) .await .execute("g.V", &[]) .await @@ -155,10 +167,11 @@ mod aio { } } + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_wrong_alias() { - let error = connect() + async fn test_wrong_alias(protocol: IoProtocol) { + let error = connect_serializer(protocol) .await .alias("foo") .execute("g.V()", &[]) @@ -174,11 +187,11 @@ mod aio { } } + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - - async fn test_vertex_query() { - let graph = connect().await; + async fn test_vertex_query(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; let vertices = graph .execute( "g.V().hasLabel('person').has('name',name)", @@ -194,10 +207,12 @@ mod aio { assert_eq!("person", vertices[0].label()); } + + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_edge_query() { - let graph = connect().await; + async fn test_edge_query(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; let edges = graph .execute("g.E().hasLabel('knows').limit(1)", &[]) .await @@ -211,10 +226,11 @@ mod aio { assert_eq!("knows", edges[0].label()); } + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_vertex_creation() { - let graph = connect().await; + async fn test_vertex_creation(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; let mark = create_vertex(&graph, "mark").await; assert_eq!("person", mark.label()); @@ -237,10 +253,11 @@ mod aio { ); } + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_edge_creation() { - let graph = connect().await; + async fn test_edge_creation(protocol: IoProtocol) { + let graph = connect_serializer(protocol).await; let mark = create_vertex(&graph, "mark").await; let frank = create_vertex(&graph, "frank").await; diff --git a/gremlin-client/tests/integration_client_async_v2.rs b/gremlin-client/tests/integration_client_async_v2.rs deleted file mode 100644 index 270eb7bd..00000000 --- a/gremlin-client/tests/integration_client_async_v2.rs +++ /dev/null @@ -1,174 +0,0 @@ -#[allow(dead_code)] -mod common; - -#[cfg(feature = "async_gremlin")] -mod aio { - - use gremlin_client::GremlinError; - use gremlin_client::{Edge, GValue, GraphSON, Map, Vertex}; - - use super::common::aio::{connect_serializer, create_edge, create_vertex}; - #[cfg(feature = "async-std-runtime")] - use async_std::prelude::*; - - #[cfg(feature = "tokio-runtime")] - use tokio_stream::StreamExt; - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_client_connection_ok_v2() { - connect_serializer(GraphSON::V2).await; - } - - #[cfg(feature = "async-std-runtime")] - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - async fn test_empty_query_v2() { - let graph = connect_serializer(GraphSON::V2).await; - - assert_eq!( - 0, - graph - .execute("g.V().hasLabel('NotFound')", &[]) - .await - .expect("It should execute a traversal") - .count() - .await - ) - } - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_wrong_query_v2() { - let error = connect_serializer(GraphSON::V2) - .await - .execute("g.V", &[]) - .await - .expect_err("it should return an error"); - - match error { - GremlinError::Request((code, message)) => { - assert_eq!(597, code); - assert_eq!("No such property: V for class: org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource",message) - } - _ => panic!("wrong error type"), - } - } - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_wrong_alias_v2() { - let error = connect_serializer(GraphSON::V2) - .await - .alias("foo") - .execute("g.V()", &[]) - .await - .expect_err("it should return an error"); - - match error { - GremlinError::Request((code, message)) => { - assert_eq!(499, code); - assert_eq!("Could not alias [g] to [foo] as [foo] not in the Graph or TraversalSource global bindings",message) - } - _ => panic!("wrong error type"), - } - } - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - - async fn test_vertex_query_v2() { - let graph = connect_serializer(GraphSON::V2).await; - - println!("About to execute query."); - let vertices = graph - .execute( - "g.V().hasLabel('person').has('name',name)", - &[("name", &"marko")], - ) - .await - .expect("it should execute a query") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .await - .expect("It should be ok"); - - assert_eq!("person", vertices[0].label()); - } - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_edge_query_v2() { - let graph = connect_serializer(GraphSON::V2).await; - let edges = graph - .execute("g.E().hasLabel('knows').limit(1)", &[]) - .await - .expect("it should execute a query") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .await - .expect("It should be ok"); - - assert_eq!("knows", edges[0].label()); - } - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_vertex_creation_v2() { - let graph = connect_serializer(GraphSON::V2).await; - let mark = create_vertex(&graph, "mark").await; - - assert_eq!("person", mark.label()); - - let value_map = graph - .execute("g.V(identity).valueMap()", &[("identity", mark.id())]) - .await - .expect("should fetch valueMap with properties") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .await - .expect("It should be ok"); - - assert_eq!(1, value_map.len()); - - assert_eq!( - Some(&GValue::List(vec![String::from("mark").into()].into())), - value_map[0].get("name") - ); - } - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_edge_creation_v2() { - let graph = connect_serializer(GraphSON::V2).await; - let mark = create_vertex(&graph, "mark").await; - let frank = create_vertex(&graph, "frank").await; - - let edge = create_edge(&graph, &mark, &frank, "knows").await; - - assert_eq!("knows", edge.label()); - - assert_eq!(&mark, edge.out_v()); - assert_eq!(&frank, edge.in_v()); - - let edges = graph - .execute("g.V(identity).outE()", &[("identity", mark.id())]) - .await - .expect("should fetch edge") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .await - .expect("It should be ok"); - - assert_eq!(1, edges.len()); - - let edge = &edges[0]; - - assert_eq!("knows", edge.label()); - - assert_eq!(&mark, edge.out_v()); - assert_eq!(&frank, edge.in_v()); - } -} diff --git a/gremlin-client/tests/integration_client_v2.rs b/gremlin-client/tests/integration_client_v2.rs deleted file mode 100644 index 47bfb18a..00000000 --- a/gremlin-client/tests/integration_client_v2.rs +++ /dev/null @@ -1,465 +0,0 @@ -mod common; - -use gremlin_client::{ - ConnectionOptions, GraphSON, GremlinClient, GremlinError, List, TlsOptions, ToGValue, - TraversalExplanation, TraversalMetrics, VertexProperty, -}; -use gremlin_client::{Edge, GKey, GValue, Map, Vertex, GID}; - -use common::io::{create_edge, create_vertex, expect_client_serializer, graph_serializer}; - -#[test] -fn test_client_connection_ok_v2() { - expect_client_serializer(GraphSON::V2); -} - -#[test] -fn test_empty_query_v2() { - assert_eq!( - 0, - graph_serializer(GraphSON::V2) - .execute("g.V().hasLabel('NotFound')", &[]) - .expect("It should execute a traversal") - .count() - ) -} - -#[test] -fn test_ok_credentials_v2() { - let client = GremlinClient::connect( - ConnectionOptions::builder() - .host("localhost") - .port(8183) - .credentials("stephen", "password") - .ssl(true) - .tls_options(TlsOptions { - accept_invalid_certs: true, - }) - .serializer(GraphSON::V2) - .build(), - ) - .expect("Cannot connect"); - - let result = client.execute("g.V().limit(1)", &[]); - assert!(result.is_ok(), "{:?}", result); -} - -#[test] -fn test_ko_credentials_v2() { - let client = GremlinClient::connect( - ConnectionOptions::builder() - .host("localhost") - .port(8183) - .credentials("stephen", "pwd") - .ssl(true) - .tls_options(TlsOptions { - accept_invalid_certs: true, - }) - .serializer(GraphSON::V2) - .build(), - ) - .expect("Cannot connect"); - - let result = client.execute("g.V().limit(1)", &[]); - assert!(result.is_err(), "{:?}", result); -} - -#[test] -fn test_wrong_query_v2() { - let error = graph_serializer(GraphSON::V2) - .execute("g.V", &[]) - .expect_err("it should return an error"); - - match error { - GremlinError::Request((code, message)) => { - assert_eq!(597, code); - assert_eq!("No such property: V for class: org.apache.tinkerpop.gremlin.process.traversal.dsl.graph.GraphTraversalSource",message) - } - _ => panic!("wrong error type"), - } -} - -#[test] -fn test_wrong_alias_v2() { - let error = graph_serializer(GraphSON::V2) - .alias("foo") - .execute("g.V()", &[]) - .expect_err("it should return an error"); - - match error { - GremlinError::Request((code, message)) => { - assert_eq!(499, code); - assert_eq!("Could not alias [g] to [foo] as [foo] not in the Graph or TraversalSource global bindings",message) - } - _ => panic!("wrong error type"), - } -} - -#[test] - -fn test_vertex_query_v2() { - let graph = graph_serializer(GraphSON::V2); - let vertices = graph - .execute( - "g.V().hasLabel('person').has('name',name)", - &[("name", &"marko")], - ) - .expect("it should execute a query") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!("person", vertices[0].label()); -} -#[test] -fn test_edge_query_v2() { - let graph = graph_serializer(GraphSON::V2); - let edges = graph - .execute("g.E().hasLabel('knows').limit(1)", &[]) - .expect("it should execute a query") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!("knows", edges[0].label()); -} - -#[test] -fn test_vertex_creation_v2() { - let graph = graph_serializer(GraphSON::V2); - let mark = create_vertex(&graph, "mark"); - - assert_eq!("person", mark.label()); - - let value_map = graph - .execute("g.V(identity).valueMap()", &[("identity", mark.id())]) - .expect("should fetch valueMap with properties") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!(1, value_map.len()); - - assert_eq!( - Some(&GValue::List(vec![String::from("mark").into()].into())), - value_map[0].get("name") - ); -} - -#[test] -fn test_inserting_date_with_milisecond_precision() { - use chrono::offset::TimeZone; - use chrono::DateTime; - use chrono::Utc; - - let graph = graph_serializer(GraphSON::V2); - - let q = r#"g.addV('person').property('dateTime',dateTime).propertyMap()"#; - - let expected = chrono::Utc.timestamp(1551825863, 0); - let params: &[(&str, &dyn ToGValue)] = &[("dateTime", &expected)]; - - let results = graph - .execute(q, params) - .expect("it should execute addV") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - let properties = &results[0]; - assert_eq!(1, properties.len()); - - assert_eq!( - &expected, - properties["dateTime"].get::().unwrap()[0] - .get::() - .unwrap() - .get::>() - .unwrap() - ); -} - -#[test] -fn test_complex_vertex_creation_with_properties_v2() { - use chrono::offset::TimeZone; - - let graph = graph_serializer(GraphSON::V2); - - let q = r#" - g.addV('person') - .property('id',UUID.randomUUID()) - .property('name',name) - .property('age',age) - .property('time',time) - .property('score',score) - .property('uuid',uuid) - .property('date',new Date(date)) - .property('dateTime',dateTime) - .propertyMap()"#; - - let uuid = uuid::Uuid::new_v4(); - let params: &[(&str, &dyn ToGValue)] = &[ - ("age", &22), - ("time", &(22 as i64)), - ("name", &"mark"), - ("score", &3.2), - ("uuid", &uuid), - ("dateTime", &chrono::Utc.timestamp(1551825863, 0)), - ("date", &(1551825863 as i64)), - ]; - let results = graph - .execute(q, params) - .expect("it should execute addV") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - let properties = &results[0]; - - assert_eq!(8, properties.len()); - - assert_eq!( - &22, - properties["age"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &22, - properties["time"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &chrono::Utc.timestamp_millis(1551825863), - properties["date"].get::().unwrap()[0] - .get::() - .unwrap() - .get::>() - .unwrap() - ); - - assert!(properties["id"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .is_ok()); - - assert_eq!( - &uuid, - properties["uuid"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &String::from("mark"), - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &3.2, - properties["score"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &chrono::Utc.timestamp(1551825863, 0), - properties["dateTime"].get::().unwrap()[0] - .get::() - .unwrap() - .get::>() - .unwrap() - ); -} - -#[test] -fn test_edge_creation_v2() { - let graph = graph_serializer(GraphSON::V2); - let mark = create_vertex(&graph, "mark"); - let frank = create_vertex(&graph, "frank"); - - let edge = create_edge(&graph, &mark, &frank, "knows"); - - assert_eq!("knows", edge.label()); - - assert_eq!(&mark, edge.out_v()); - assert_eq!(&frank, edge.in_v()); - - let edges = graph - .execute("g.V(identity).outE()", &[("identity", mark.id())]) - .expect("should fetch edge") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!(1, edges.len()); - - let edge = &edges[0]; - - assert_eq!("knows", edge.label()); - - assert_eq!(&mark, edge.out_v()); - assert_eq!(&frank, edge.in_v()); -} - -#[test] -fn test_profile_v2() { - let graph = graph_serializer(GraphSON::V2); - - let metrics = graph - .execute("g.V().limit(1).profile()", &[]) - .expect("should return a profile") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!(1, metrics.len()); - - let t = &metrics[0]; - - assert_eq!(true, t.duration() > &0.0); - - let steps = t.metrics(); - - assert_ne!(0, steps.len()); - - assert_eq!( - 100.0, - steps - .iter() - .map(|s| s.perc_duration()) - .fold(0.0, |acc, x| acc + x) - .round() - ); -} - -#[test] -fn test_explain_v2() { - let graph = graph_serializer(GraphSON::V2); - - let metrics = graph - .execute("g.V().limit(1).explain()", &[]) - .expect("should return a profile") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!(1, metrics.len()); - - let t = &metrics[0]; - - assert_eq!( - &vec![ - String::from("GraphStep(vertex,[])"), - String::from("RangeGlobalStep(0,1)") - ], - t.original() - ); - - assert_eq!( - &vec![ - String::from("TinkerGraphStep(vertex,[])"), - String::from("RangeGlobalStep(0,1)"), - String::from("ReferenceElementStep") - ], - t.final_t() - ); -} - -#[test] - -fn test_group_count_vertex_v2() { - let graph = graph_serializer(GraphSON::V2); - let mark = create_vertex(&graph, "mark"); - let frank = create_vertex(&graph, "frank"); - - println!("FRANK: {:#?}", frank); - - create_edge(&graph, &mark, &frank, "knows"); - - let map = graph - .execute( - "g.V(identity).out().groupCount()", - &[("identity", mark.id())], - ) - .expect("should fetch a groupCount") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - println!("MAP IS: {:#?}", map); - - assert_eq!(1, map.len()); - - let first = &map[0]; - - assert_eq!(1, first.len()); - - let count = first.get(GKey::String(match frank.id() { - GID::String(s) => s.to_string(), - GID::Int32(i) => i.to_string(), - GID::Int64(i) => i.to_string(), - })); - - assert_eq!(Some(&GValue::Int64(1)), count); -} - -#[test] - -fn test_group_count_edge_v2() { - let graph = graph_serializer(GraphSON::V2); - let mark = create_vertex(&graph, "mark"); - let frank = create_vertex(&graph, "frank"); - - let edge = create_edge(&graph, &mark, &frank, "knows"); - - let map = graph - .execute( - "g.V(identity).outE().groupCount()", - &[("identity", mark.id())], - ) - .expect("should fetch a groupCount") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - assert_eq!(1, map.len()); - - let first = &map[0]; - - assert_eq!(1, first.len()); - - let count = first.get(GKey::String(match edge.id() { - GID::String(s) => s.to_string(), - GID::Int32(i) => i.to_string(), - GID::Int64(i) => i.to_string(), - })); - - assert_eq!(Some(&GValue::Int64(1)), count); -} diff --git a/gremlin-client/tests/integration_traversal.rs b/gremlin-client/tests/integration_traversal.rs index 78c4725a..010649c4 100644 --- a/gremlin-client/tests/integration_traversal.rs +++ b/gremlin-client/tests/integration_traversal.rs @@ -1,3 +1,4 @@ +use core::panic; use std::collections::HashMap; use std::convert::TryInto; @@ -7,12 +8,18 @@ use gremlin_client::structure::{ Cardinality, Column, List, Map, Pop, TextP, Vertex, VertexProperty, P, T, }; -use gremlin_client::{utils, GKey, GValue}; +use gremlin_client::{utils, BorrowFromGValue, GKey, GValue, GremlinError, IoProtocol}; mod common; +use rstest::rstest; +use rstest_reuse::{self, *}; + +use serial_test::serial; + use common::io::{ - create_edge, create_vertex, create_vertex_with_label, drop_edges, drop_vertices, graph, + create_edge, create_vertex, create_vertex_with_label, drop_edges, drop_vertices, + graph_serializer, }; #[cfg(feature = "merge_tests")] @@ -25,9 +32,15 @@ mod merge_tests { }; use std::collections::HashMap; - #[test] - fn test_merge_v_no_options() { - let client = graph(); + #[apply(common::serializers)] + #[serial(test_merge_v_no_options)] + fn test_merge_v_no_options(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); let test_vertex_label = "test_merge_v_no_options"; drop_vertices(&client, test_vertex_label) .expect("Failed to drop vertices in case of rerun"); @@ -60,9 +73,15 @@ mod merge_tests { assert_map_property(&vertex_properties, "propertyKey", "propertyValue"); } - #[test] - fn test_merge_v_options() { - let client = graph(); + #[apply(common::serializers)] + #[serial(test_merge_v_options)] + fn test_merge_v_options(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); let expected_label = "test_merge_v_options"; drop_vertices(&client, expected_label).expect("Failed to drop vertices"); let g = traversal().with_remote(client); @@ -111,9 +130,15 @@ mod merge_tests { assert_map_property(&on_match_vertex_map, prop_key, expected_on_match_prop_value); } - #[test] - fn test_merge_v_start_step() { - let client = graph(); + #[apply(common::serializers)] + #[serial(test_merge_v_start_step)] + fn test_merge_v_start_step(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); let expected_label = "test_merge_v_start_step"; drop_vertices(&client, &expected_label).expect("Failed to drop vertiecs"); let g = traversal().with_remote(client); @@ -128,9 +153,15 @@ mod merge_tests { assert_eq!(expected_label, actual_vertex.label()) } - #[test] - fn test_merge_v_anonymous_traversal() { - let client = graph(); + #[apply(common::serializers)] + #[serial(test_merge_v_anonymous_traversal)] + fn test_merge_v_anonymous_traversal(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); let expected_label = "test_merge_v_anonymous_traversal"; drop_vertices(&client, &expected_label).expect("Failed to drop vertiecs"); let g = traversal().with_remote(client); @@ -146,9 +177,15 @@ mod merge_tests { assert_eq!(expected_label, actual_vertex.label()) } - #[test] - fn test_merge_e_start_step() { - let client = graph(); + #[apply(common::serializers)] + #[serial(test_merge_e_start_step)] + fn test_merge_e_start_step(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); let expected_vertex_label = "test_merge_e_start_step_vertex"; let expected_edge_label = "test_merge_e_start_step_edge"; let expected_edge_property_key = "test_merge_e_start_step_edge_prop"; @@ -213,9 +250,15 @@ mod merge_tests { assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } - #[test] - fn test_merge_e_no_options() { - let client = graph(); + #[apply(common::serializers)] + #[serial(test_merge_e_no_options)] + fn test_merge_e_no_options(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); let expected_vertex_label = "test_merge_e_no_options_vertex"; let expected_edge_label = "test_merge_e_no_options_edge"; let expected_edge_property_key = "test_merge_e_no_options_edge_prop"; @@ -282,9 +325,15 @@ mod merge_tests { assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } - #[test] - fn test_merge_e_options() { - let client = graph(); + #[apply(common::serializers)] + #[serial(test_merge_e_options)] + fn test_merge_e_options(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); let expected_vertex_label = "test_merge_e_options_vertex"; let expected_edge_label = "test_merge_e_options_edge"; let expected_edge_property_key = "test_merge_e_options_edge_prop"; @@ -352,11 +401,17 @@ mod merge_tests { ); } - #[test] - fn test_merge_e_anonymous_traversal() { - let client = graph(); - let expected_vertex_label = "test_merge_e_options_vertex"; - let expected_edge_label = "test_merge_e_options_edge"; + #[apply(common::serializers)] + #[serial(test_merge_e_anonymous_traversal)] + fn test_merge_e_anonymous_traversal(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); + let expected_vertex_label = "test_merge_e_anonymous_traversal_vertex"; + let expected_edge_label = "test_merge_e_anonymous_traversal_edge"; drop_vertices(&client, &expected_vertex_label).expect("Failed to drop vertiecs"); let g = traversal().with_remote(client); @@ -380,7 +435,7 @@ mod merge_tests { let anonymous_merge_e_properties = g .inject(1) .unfold() - .coalesce::([__.merge_e(assignment_map)]) + .coalesce::([__.merge_e(assignment_map.clone())]) .element_map(()) .next() .expect("Should get a response") @@ -407,10 +462,17 @@ mod merge_tests { assert_eq!(outgoing_vertex_id, &vertex_b.id().to_gvalue()); } - #[test] - fn test_merge_v_into_merge_e() { + #[apply(common::serializers)] + #[serial(test_merge_v_into_merge_e)] + fn test_merge_v_into_merge_e(protocol: IoProtocol) { + if protocol == IoProtocol::GraphSONV2 { + //GraphSONV2 doesn't support the non-string key of the merge step, + //so skip it in testing + return; + } + let client = graph_serializer(protocol); //Based on the reference doc's combo example - let client = graph(); + let expected_vertex_label = "test_merge_v_into_merge_e_vertex"; let expected_edge_label = "test_merge_v_into_merge_e_edge"; drop_vertices(&client, &expected_vertex_label).expect("Failed to drop vertiecs"); @@ -472,18 +534,22 @@ mod merge_tests { } } -#[test] -fn test_simple_vertex_traversal() { - let g = traversal().with_remote(graph()); +#[apply(common::serializers)] +#[serial(test_simple_vertex_traversal)] +fn test_simple_vertex_traversal(protocol: IoProtocol) { + let client = graph_serializer(protocol); + let g = traversal().with_remote(client); let results = g.v(()).to_list().unwrap(); assert!(results.len() > 0); } -#[test] -fn test_inject() { - let g = traversal().with_remote(graph()); +#[apply(common::serializers)] +#[serial(test_inject)] +fn test_inject(protocol: IoProtocol) { + let client = graph_serializer(protocol); + let g = traversal().with_remote(client); let expected_value = "foo"; let response: String = g .inject(vec![expected_value.into()]) @@ -495,10 +561,10 @@ fn test_inject() { assert_eq!(expected_value, response); } -#[test] -fn test_simple_vertex_traversal_with_id() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_simple_vertex_traversal_with_id)] +fn test_simple_vertex_traversal_with_id(protocol: IoProtocol) { + let client = graph_serializer(protocol); let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -510,9 +576,10 @@ fn test_simple_vertex_traversal_with_id() { assert_eq!(vertex.id(), results[0].id()); } -#[test] -fn test_simple_vertex_traversal_with_multiple_id() { - let client = graph(); +#[apply(common::serializers)] +#[serial(test_simple_vertex_traversal_with_multiple_id)] +fn test_simple_vertex_traversal_with_multiple_id(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_simple_vertex_traversal").unwrap(); let vertex = create_vertex_with_label(&client, "test_simple_vertex_traversal", "Traversal"); @@ -528,10 +595,10 @@ fn test_simple_vertex_traversal_with_multiple_id() { assert_eq!(vertex2.id(), results[1].id()); } -#[test] -fn test_simple_vertex_traversal_with_label() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_simple_vertex_traversal_with_label)] +fn test_simple_vertex_traversal_with_label(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_simple_vertex_traversal_with_label").unwrap(); let vertex = create_vertex_with_label( @@ -553,10 +620,10 @@ fn test_simple_vertex_traversal_with_label() { assert_eq!(vertex.id(), results[0].id()); } -#[test] -fn test_simple_vertex_traversal_with_label_and_has() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_simple_vertex_traversal_with_label_and_has)] +fn test_simple_vertex_traversal_with_label_and_has(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_simple_vertex_traversal_with_label_and_has").unwrap(); let vertex = create_vertex_with_label( @@ -621,19 +688,21 @@ fn test_simple_vertex_traversal_with_label_and_has() { assert_eq!(vertex.id(), results[0].id()); } -#[test] -fn test_simple_edge_traversal() { - let g = traversal().with_remote(graph()); +#[apply(common::serializers)] +#[serial(test_simple_edge_traversal)] +fn test_simple_edge_traversal(protocol: IoProtocol) { + let client = graph_serializer(protocol); + let g = traversal().with_remote(client); let results = g.e(()).to_list().unwrap(); assert!(results.len() > 0); } -#[test] -fn test_simple_edge_traversal_id() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_simple_edge_traversal_id)] +fn test_simple_edge_traversal_id(protocol: IoProtocol) { + let client = graph_serializer(protocol); let v = create_vertex(&client, "Traversal"); let v1 = create_vertex(&client, "Traversal"); @@ -648,10 +717,10 @@ fn test_simple_edge_traversal_id() { assert_eq!(e.id(), results[0].id()); } -#[test] -fn test_simple_edge_traversal_with_label() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_simple_edge_traversal_with_label)] +fn test_simple_edge_traversal_with_label(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_edges(&client, "test_simple_edge_traversal_with_label").unwrap(); let v = create_vertex(&client, "Traversal"); @@ -672,10 +741,10 @@ fn test_simple_edge_traversal_with_label() { assert_eq!(e.id(), results[0].id()); } -#[test] -fn test_traversal() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_traversal)] +fn test_traversal(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_edges(&client, "test_vertex_out_traversal").unwrap(); let v = create_vertex(&client, "Traversal"); @@ -815,9 +884,11 @@ fn test_traversal() { assert_eq!(0, results.len()); } -#[test] -fn test_add_v() { - let g = traversal().with_remote(graph()); +#[apply(common::serializers)] +#[serial(test_add_v)] +fn test_add_v(protocol: IoProtocol) { + let client = graph_serializer(protocol); + let g = traversal().with_remote(client); let results = g.add_v("person").to_list().unwrap(); @@ -833,9 +904,10 @@ fn test_add_v() { assert_eq!("vertex", results[0].label()); } -#[test] -fn test_add_v_with_properties() { - let client = graph(); +#[apply(common::serializers)] +#[serial(test_add_v_with_properties)] +fn test_add_v_with_properties(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client.clone()); let results = g @@ -878,10 +950,10 @@ fn test_add_v_with_properties() { ); } -#[test] -fn test_add_v_with_property_many() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_add_v_with_property_many)] +fn test_add_v_with_property_many(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_add_v_with_property_many").unwrap(); let g = traversal().with_remote(client.clone()); @@ -928,10 +1000,10 @@ fn test_add_v_with_property_many() { ); } -#[test] -fn test_has_many() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_has_many)] +fn test_has_many(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_has_many").unwrap(); let g = traversal().with_remote(client.clone()); @@ -961,9 +1033,10 @@ fn test_has_many() { assert_eq!(results.len(), 1); } -#[test] -fn test_add_e() { - let client = graph(); +#[apply(common::serializers)] +#[serial(test_add_e)] +fn test_add_e(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client.clone()); let v = g @@ -1024,10 +1097,10 @@ fn test_add_e() { assert_eq!("knows", edges[0].label()); } -#[test] -fn test_label_step() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_label_step)] +fn test_label_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -1039,10 +1112,10 @@ fn test_label_step() { assert_eq!("person", results[0]); } -#[test] -fn test_properties_step() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_properties_step)] +fn test_properties_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -1064,10 +1137,10 @@ fn test_properties_step() { assert_eq!(0, results.len()); } -#[test] -fn test_property_map() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_property_map)] +fn test_property_map(protocol: IoProtocol) { + let client = graph_serializer(protocol); let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -1111,10 +1184,10 @@ fn test_property_map() { assert_eq!(0, properties.len()); } -#[test] -fn test_values() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_values)] +fn test_values(protocol: IoProtocol) { + let client = graph_serializer(protocol); let vertex = create_vertex(&client, "Traversal"); let g = traversal().with_remote(client); @@ -1140,10 +1213,10 @@ fn test_values() { assert_eq!(0, results.len()); } -#[test] -fn test_value_map() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_value_map)] +fn test_value_map(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client); let vertices = g @@ -1162,9 +1235,7 @@ fn test_value_map() { assert_eq!( "test", - value["name"].get::().unwrap()[0] - .get::() - .unwrap() + get_map::(value, "name").unwrap().unwrap() ); let results = g.v(vertex.id()).value_map("name").to_list().unwrap(); @@ -1175,9 +1246,7 @@ fn test_value_map() { assert_eq!( "test", - value["name"].get::().unwrap()[0] - .get::() - .unwrap() + get_map::(value, "name").unwrap().unwrap() ); let results = g.v(vertex.id()).value_map("fake").to_list().unwrap(); @@ -1185,16 +1254,27 @@ fn test_value_map() { assert_eq!(0, results[0].len()); let results = g.v(vertex.id()).value_map(true).to_list().unwrap(); + assert_eq!(1, results.len()); + let value = &results[0]; - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); + assert_eq!( + Some(vertex.id().get().unwrap()), + get_map_id(&value).unwrap() + ); + assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); + assert_eq!( + Some("test".to_owned()).as_ref(), + get_map(&value, "name").unwrap() + ); + assert!(results[0].get("id").is_some()); + assert!(results[0].get("label").is_some()); assert_eq!(true, results[0].get("name").is_some()); } -#[test] -fn test_unwrap_map() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_unwrap_map)] +fn test_unwrap_map(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client); let vertices = g @@ -1208,23 +1288,18 @@ fn test_unwrap_map() { let results = g.v(vertex.id()).value_map(true).next().unwrap().unwrap(); let v_id = vertex.id().get::().unwrap(); - let id = utils::unwrap_map::(&results, "id", 0); - let property = utils::unwrap_map::(&results, "name", 0); - let label = utils::unwrap_map::(&results, "label", 0); - - assert_eq!(id.is_ok(), true); - assert_eq!(property.is_ok(), true); - assert_eq!(label.is_ok(), true); - - assert_eq!(id.unwrap(), v_id); + let id = get_map_id(&results).unwrap(); + let property = get_map::(&results, "name").unwrap(); + let label = get_map_label(&results).unwrap(); + assert_eq!(id, Some(v_id)); assert_eq!(property.unwrap(), "test"); - assert_eq!(label.unwrap(), "test_value_map"); + assert_eq!(label, Some(vertex.label())); } -#[test] -fn test_element_map() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_element_map)] +fn test_element_map(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client); let vertices = g @@ -1252,22 +1327,39 @@ fn test_element_map() { assert_eq!("test", value["name"].get::().unwrap()); let results = g.v(vertex.id()).element_map("fake").to_list().unwrap(); + let value = &results[0]; + assert_eq!(2, value.len()); + assert_eq!( + Some(vertex.id().get().unwrap()), + get_map_id(&value).unwrap() + ); + assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); assert_eq!(2, results[0].len()); - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); + assert!(results[0].get("id").is_some()); + assert!(results[0].get("label").is_some()); let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); + let value = &results[0]; - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); + assert_eq!( + Some(vertex.id().get().unwrap()), + get_map_id(&value).unwrap() + ); + assert_eq!(Some(vertex.label()), get_map_label(&value).unwrap()); + assert_eq!( + Some("test".to_owned()).as_ref(), + get_map(&value, "name").unwrap() + ); + assert!(results[0].get("id").is_some()); + assert!(results[0].get("label").is_some()); assert_eq!(true, results[0].get("name").is_some()); } -#[test] -fn test_count() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_count)] +fn test_count(protocol: IoProtocol) { + let client = graph_serializer(protocol); let vertex = create_vertex_with_label(&client, "test_count", "Count"); let g = traversal().with_remote(client); @@ -1281,10 +1373,10 @@ fn test_count() { assert_eq!(&1, value); } -#[test] -fn test_group_count_step() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_group_count_step)] +fn test_group_count_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_group_count").unwrap(); let vertex = create_vertex_with_label(&client, "test_group_count", "Count"); @@ -1295,14 +1387,36 @@ fn test_group_count_step() { .v(()) .has_label("test_group_count") .group_count() - .to_list() + //Normalize the keys to a common type of Id across serializers + .by(T::Id) + .next() .unwrap(); - assert_eq!(1, results.len()); + let value = results.expect("Should have returned a map"); + assert_eq!(1, value.len(), "Should only have 1 entry in map"); + let (actual_key, actual_value) = value.into_iter().next().unwrap(); - let value = &results[0]; + //The actual key may come back as either an int or a string depending + //on the serializer, so normalize it and the vertex gid to a string + let normalized_actual_key = match actual_key { + GKey::String(v) => v, + GKey::Int64(v) => v.to_string(), + other => panic!("Unexpected key type: {:?}", other), + }; + + let normalized_vertex_id = match vertex.id() { + gremlin_client::GID::String(v) => v.clone(), + gremlin_client::GID::Int32(v) => v.to_string(), + gremlin_client::GID::Int64(v) => v.to_string(), + }; - assert_eq!(&1, value[&vertex].get::().unwrap()); + assert_eq!(normalized_actual_key, normalized_vertex_id); + + assert_eq!( + actual_value, + GValue::Int64(1), + "Group count should have been the single vertex" + ); let results = g .v(()) @@ -1333,10 +1447,10 @@ fn test_group_count_step() { assert_eq!(&1, value["test_group_count"].get::().unwrap()); } -#[test] -fn test_group_by_step() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_group_by_step)] +fn test_group_by_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_group_by_step").unwrap(); create_vertex_with_label(&client, "test_group_by_step", "Count"); @@ -1389,10 +1503,10 @@ fn test_group_by_step() { assert_eq!(&1, value["test_group_by_step"].get::().unwrap()); } -#[test] -fn test_select_step() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_select_step)] +fn test_select_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_select_step").unwrap(); create_vertex_with_label(&client, "test_select_step", "Count"); @@ -1415,10 +1529,10 @@ fn test_select_step() { assert_eq!(&1, value.get::().unwrap()); } -#[test] -fn test_fold_step() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_fold_step)] +fn test_fold_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_fold_step").unwrap(); create_vertex_with_label(&client, "test_fold_step", "Count"); @@ -1440,10 +1554,10 @@ fn test_fold_step() { assert_eq!("Count", value[0].get::().unwrap()); } -#[test] -fn test_unfold_step() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_unfold_step)] +fn test_unfold_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_unfold_step").unwrap(); let vertex = create_vertex_with_label(&client, "test_unfold_step", "Count"); @@ -1471,10 +1585,10 @@ fn test_unfold_step() { ); } -#[test] -fn test_path_step() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_path_step)] +fn test_path_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_path_step").unwrap(); let v = create_vertex_with_label(&client, "test_path_step", "Count"); @@ -1495,10 +1609,10 @@ fn test_path_step() { assert_eq!(v.id(), value.objects()[0].get::().unwrap().id()); } -#[test] -fn test_limit_step() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_limit_step)] +fn test_limit_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_limit_step").unwrap(); create_vertex_with_label(&client, "test_limit_step", "Count"); @@ -1516,10 +1630,10 @@ fn test_limit_step() { assert_eq!(1, results.len()); } -#[test] -fn test_dedup_step() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_dedup_step)] +fn test_dedup_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_limit_step").unwrap(); create_vertex_with_label(&client, "test_limit_step", "Count"); @@ -1538,10 +1652,10 @@ fn test_dedup_step() { assert_eq!(1, results.len()); } -#[test] -fn test_numerical_steps() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_numerical_steps)] +fn test_numerical_steps(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_numerical_steps").unwrap(); let g = traversal().with_remote(client); @@ -1610,10 +1724,10 @@ fn test_numerical_steps() { assert_eq!(&20, results[0].get::().unwrap()); } -#[test] -fn test_has_with_p_steps() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_has_with_p_steps)] +fn test_has_with_p_steps(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_has_with_p_steps").unwrap(); let g = traversal().with_remote(client); @@ -1650,6 +1764,29 @@ fn test_has_with_p_steps() { assert_eq!(&20, results[0].get::().unwrap()); + let results = g + .v(()) + .has(("test_has_with_p_steps", "age", 20)) + .values("age") + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(&20, results[0].get::().unwrap()); + + let results = g + .v(()) + .has_label("test_has_with_p_steps") + .values("age") + .where_(__.is(P::eq(20))) + .to_list() + .unwrap(); + + assert_eq!(1, results.len()); + + assert_eq!(&20, results[0].get::().unwrap()); + let results = g .v(()) .has_label("test_has_with_p_steps") @@ -1663,10 +1800,10 @@ fn test_has_with_p_steps() { assert_eq!(&20, results[0].get::().unwrap()); } -#[test] -fn test_has_with_text_p_step() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_has_with_text_p_step)] +fn test_has_with_text_p_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_has_with_text_p_step").unwrap(); let g = traversal().with_remote(client); @@ -1730,10 +1867,10 @@ fn test_has_with_text_p_step() { assert_eq!(2, results.len()); } -#[test] -fn where_step_test() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(where_step_test)] +fn where_step_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "where_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1756,10 +1893,10 @@ fn where_step_test() { assert_eq!(v[0].id(), results[0].id()); } -#[test] -fn not_step_test() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(not_step_test)] +fn not_step_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "not_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1779,10 +1916,10 @@ fn not_step_test() { assert_eq!(0, results.len()); } -#[test] -fn order_step_test() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(order_step_test)] +fn order_step_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "order_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1823,10 +1960,10 @@ fn order_step_test() { assert_eq!("b", results[0].get::().unwrap()); } -#[test] -fn match_step_test() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(match_step_test)] +fn match_step_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "match_step_test").unwrap(); drop_edges(&client, "match_step_test_edge").unwrap(); @@ -1885,10 +2022,10 @@ fn match_step_test() { assert_eq!(&v3[0], first["c"].get::().unwrap()); } -#[test] -fn drop_step_test() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(drop_step_test)] +fn drop_step_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "drop_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1918,10 +2055,10 @@ fn drop_step_test() { assert_eq!(false, results); } -#[test] -fn or_step_test() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(or_step_test)] +fn or_step_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "or_step_test").unwrap(); let g = traversal().with_remote(client); @@ -1959,10 +2096,10 @@ fn or_step_test() { assert_eq!(result.len(), 2); } -#[test] -fn iter_terminator_test() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(iter_terminator_test)] +fn iter_terminator_test(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "iter_terminator_test").unwrap(); let g = traversal().with_remote(client); @@ -1988,10 +2125,10 @@ fn iter_terminator_test() { assert_eq!(2, results.len()) } -#[test] -fn test_select_pop() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_select_pop)] +fn test_select_pop(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_select_pop").unwrap(); drop_vertices(&client, "test_select_pop_child").unwrap(); @@ -2077,10 +2214,10 @@ fn test_select_pop() { assert_eq!(results.len(), 1); } -#[test] -fn test_repeat_until_loops_loops() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_repeat_until_loops_loops)] +fn test_repeat_until_loops_loops(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_repeat_until_loops").unwrap(); drop_vertices(&client, "test_repeat_until_loops_child").unwrap(); @@ -2118,10 +2255,30 @@ fn test_repeat_until_loops_loops() { assert_eq!(results[0], e2[0]); } -#[test] -fn test_simple_path() { - let client = graph(); +#[apply(common::serializers)] +#[serial(test_simple_path)] +fn test_simple_vertex_property(protocol: IoProtocol) { + let client = graph_serializer(protocol); + drop_vertices(&client, "test_simple_vertex_property").unwrap(); + + let g = traversal().with_remote(client); + + let v = g + .add_v("test_simple_vertex_property") + .property("name", "a") + .element_map(()) + .next() + .unwrap() + .unwrap(); + + let actual_property: &String = v.get("name").expect("Should have property").get().unwrap(); + assert_eq!(actual_property, "a"); +} +#[apply(common::serializers)] +#[serial(test_simple_path)] +fn test_simple_path(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_simple_path").unwrap(); drop_vertices(&client, "test_simple_path_child").unwrap(); @@ -2160,10 +2317,10 @@ fn test_simple_path() { assert_eq!(results[0], e2[0]); } -#[test] -fn test_sample() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_sample)] +fn test_sample(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_sample").unwrap(); drop_vertices(&client, "test_sample_child").unwrap(); @@ -2193,10 +2350,10 @@ fn test_sample() { assert_eq!(results.len(), 1); } -#[test] -fn test_local() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_local)] +fn test_local(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_local").unwrap(); drop_vertices(&client, "test_local_child").unwrap(); drop_vertices(&client, "test_local_child_child").unwrap(); @@ -2269,9 +2426,10 @@ fn test_local() { assert_eq!(results.len(), 2); } -#[test] -fn test_side_effect() { - let client = graph(); +#[apply(common::serializers)] +#[serial(test_side_effect)] +fn test_side_effect(protocol: IoProtocol) { + let client = graph_serializer(protocol); let test_vertex_label = "test_side_effect"; let expected_side_effect_key = "prop_key"; let expected_side_effect_value = "prop_val"; @@ -2298,9 +2456,10 @@ fn test_side_effect() { ); } -#[test] -fn test_anonymous_traversal_properties_drop() { - let client = graph(); +#[apply(common::serializers)] +#[serial(test_anonymous_traversal_properties_drop)] +fn test_anonymous_traversal_properties_drop(protocol: IoProtocol) { + let client = graph_serializer(protocol); let test_vertex_label = "test_anonymous_traversal_properties_drop"; let pre_drop_prop_key = "pre_drop_prop_key"; let expected_prop_value = "prop_val"; @@ -2353,9 +2512,10 @@ fn test_anonymous_traversal_properties_drop() { ); } -#[test] -fn test_by_columns() { - let client = graph(); +#[apply(common::serializers)] +#[serial(test_by_columns)] +fn test_by_columns(protocol: IoProtocol) { + let client = graph_serializer(protocol); let test_vertex_label = "test_by_columns"; let expected_side_effect_key_a = "prop_key_a"; let expected_side_effect_value_a = "prop_val_a"; @@ -2409,10 +2569,10 @@ fn test_by_columns() { ); } -#[test] -fn test_property_cardinality() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_property_cardinality)] +fn test_property_cardinality(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_property_cardinality").unwrap(); let g = traversal().with_remote(client); @@ -2440,10 +2600,10 @@ fn test_property_cardinality() { assert_eq!(1, new_v["name"].get::().unwrap().len()); } -#[test] -fn test_choose() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_choose)] +fn test_choose(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_choose").unwrap(); let g = traversal().with_remote(client); @@ -2482,9 +2642,10 @@ fn test_choose() { assert_eq!(success_vertices.is_some(), true); } -#[test] -fn test_choose_by_literal_options() { - let client = graph(); +#[apply(common::serializers)] +#[serial(test_choose_by_literal_options)] +fn test_choose_by_literal_options(protocol: IoProtocol) { + let client = graph_serializer(protocol); let g = traversal().with_remote(client); let choosen_literal_a = g @@ -2510,10 +2671,10 @@ fn test_choose_by_literal_options() { assert_eq!(choosen_literal_b, Some("option-b".into())); } -#[test] -fn test_coalesce() { - let client = graph(); - +#[apply(common::serializers)] +#[serial()] +fn test_coalesce(protocol: IoProtocol) { + let client = graph_serializer(protocol); use gremlin_client::GValue; drop_vertices(&client, "test_coalesce").unwrap(); @@ -2546,10 +2707,10 @@ fn test_coalesce() { assert!(values.contains(&String::from("b"))); } -#[test] -fn test_coalesce_unfold() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_coalesce_unfold)] +fn test_coalesce_unfold(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_coalesce_unfold").unwrap(); let g = traversal().with_remote(client); @@ -2603,10 +2764,10 @@ fn test_coalesce_unfold() { ); } -#[test] -fn test_none_step() { - let client = graph(); - +#[apply(common::serializers)] +#[serial(test_none_step)] +fn test_none_step(protocol: IoProtocol) { + let client = graph_serializer(protocol); drop_vertices(&client, "test_none_step").unwrap(); let g = traversal().with_remote(client); @@ -2631,15 +2792,47 @@ fn test_none_step() { assert_eq!(1, vertex_count); } -#[test] +fn get_map_id<'a>(map: &'a Map) -> Result, GremlinError> { + let string_keyed = get_map(map, "id")?; + if string_keyed.is_some() { + Ok(string_keyed) + } else { + get_map(map, T::Id) + } +} + +fn get_map_label<'a>(map: &'a Map) -> Result, GremlinError> { + let string_keyed = get_map(map, "label")?; + if string_keyed.is_some() { + Ok(string_keyed) + } else { + get_map(map, T::Label) + } +} + +fn get_map<'a, T, K>(map: &'a Map, key: K) -> Result, GremlinError> +where + T: BorrowFromGValue, + K: Into, +{ + map.get(key) + .map(|val| match val { + GValue::List(list) => list[0].get::(), + other => other.get::(), + }) + .transpose() +} + +#[apply(common::serializers)] +#[serial(test_traversal_vertex_mapping)] #[cfg(feature = "derive")] -fn test_traversal_vertex_mapping() { +fn test_traversal_vertex_mapping(protocol: IoProtocol) { + let client = graph_serializer(protocol); use chrono::{DateTime, TimeZone, Utc}; use gremlin_client::derive::FromGMap; + use gremlin_client::process::traversal::{Bytecode, TraversalBuilder}; use std::convert::TryFrom; - let client = graph(); - drop_vertices(&client, "test_vertex_mapping").unwrap(); let g = traversal().with_remote(client); @@ -2661,6 +2854,7 @@ fn test_traversal_vertex_mapping() { #[derive(Debug, PartialEq, FromGMap)] struct Person { + label: String, name: String, age: i32, time: i64, @@ -2668,11 +2862,11 @@ fn test_traversal_vertex_mapping() { uuid: uuid::Uuid, optional: Option, } - let person = Person::try_from(mark.unwrap().unwrap()); - assert_eq!(person.is_ok(), true); + let person = Person::try_from(mark.unwrap().unwrap()).expect("Should get person"); assert_eq!( Person { + label: String::from("person"), name: String::from("Mark"), age: 22, time: 22, @@ -2680,6 +2874,6 @@ fn test_traversal_vertex_mapping() { uuid: uuid, optional: None }, - person.unwrap() + person ); } diff --git a/gremlin-client/tests/integration_traversal_async.rs b/gremlin-client/tests/integration_traversal_async.rs index d0a99b0b..84213119 100644 --- a/gremlin-client/tests/integration_traversal_async.rs +++ b/gremlin-client/tests/integration_traversal_async.rs @@ -2,9 +2,16 @@ mod common; #[cfg(feature = "async_gremlin")] mod aio { + use rstest::*; + use rstest_reuse::{self, *}; + + use serial_test::serial; + use gremlin_client::process::traversal::traversal; - use super::common::aio::{connect, create_vertex_with_label, drop_vertices}; + use crate::common; + + use super::common::aio::{connect_serializer, create_vertex_with_label, drop_vertices}; #[cfg(feature = "async-std-runtime")] use async_std::prelude::*; @@ -12,12 +19,14 @@ mod aio { #[cfg(feature = "tokio-runtime")] use tokio_stream::StreamExt; - use gremlin_client::Vertex; + use gremlin_client::{IoProtocol, Vertex}; + #[apply(common::serializers)] #[cfg_attr(feature = "async-std-runtime", async_std::test)] #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_simple_vertex_traversal_with_multiple_id() { - let client = connect().await; + #[serial(test_simple_vertex_traversal_with_multiple_id)] + async fn test_simple_vertex_traversal_with_multiple_id(protocol: IoProtocol) { + let client = connect_serializer(protocol).await; drop_vertices(&client, "test_simple_vertex_traversal_async") .await .unwrap(); diff --git a/gremlin-client/tests/integration_traversal_async_v2.rs b/gremlin-client/tests/integration_traversal_async_v2.rs deleted file mode 100644 index b7ef1c70..00000000 --- a/gremlin-client/tests/integration_traversal_async_v2.rs +++ /dev/null @@ -1,77 +0,0 @@ -#[allow(dead_code)] -mod common; - -#[cfg(feature = "async_gremlin")] -mod aio { - use gremlin_client::process::traversal::traversal; - - use super::common::aio::{connect_serializer, create_vertex_with_label, drop_vertices}; - - #[cfg(feature = "async-std-runtime")] - use async_std::prelude::*; - - #[cfg(feature = "tokio-runtime")] - use tokio_stream::StreamExt; - - use gremlin_client::{GraphSON, Vertex}; - - #[cfg_attr(feature = "async-std-runtime", async_std::test)] - #[cfg_attr(feature = "tokio-runtime", tokio::test)] - async fn test_simple_vertex_traversal_with_multiple_id_v2() { - let client = connect_serializer(GraphSON::V2).await; - drop_vertices(&client, "test_simple_vertex_traversal_async") - .await - .unwrap(); - - let vertex = - create_vertex_with_label(&client, "test_simple_vertex_traversal_async", "Traversal") - .await; - let vertex2 = - create_vertex_with_label(&client, "test_simple_vertex_traversal_async", "Traversal") - .await; - - let g = traversal().with_remote_async(client); - - let results = g - .v(vec![vertex.id(), vertex2.id()]) - .to_list() - .await - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - assert_eq!(vertex2.id(), results[1].id()); - - let has_next = g - .v(()) - .has_label("test_simple_vertex_traversal_async") - .has_next() - .await - .expect("It should return"); - - assert_eq!(true, has_next); - - let next = g - .v(()) - .has_label("test_simple_vertex_traversal_async") - .next() - .await - .expect("It should execute one traversal") - .expect("It should return one element"); - - assert_eq!("test_simple_vertex_traversal_async", next.label()); - - let vertices = g - .v(()) - .has_label("test_simple_vertex_traversal_async") - .iter() - .await - .expect("It should get the iterator") - .collect::, _>>() - .await - .expect("It should collect elements"); - - assert_eq!(2, vertices.len()); - } -} diff --git a/gremlin-client/tests/integration_traversal_v2.rs b/gremlin-client/tests/integration_traversal_v2.rs deleted file mode 100644 index 50e3ec00..00000000 --- a/gremlin-client/tests/integration_traversal_v2.rs +++ /dev/null @@ -1,1837 +0,0 @@ -use gremlin_client::process::traversal::{traversal, Order, __}; -use gremlin_client::structure::{ - Cardinality, GKey, GValue, List, Map, Pop, TextP, Vertex, VertexProperty, GID, P, T, -}; -use gremlin_client::{utils, GraphSON}; - -mod common; - -use common::io::{ - create_edge, create_vertex, create_vertex_with_label, drop_edges, drop_vertices, - graph_serializer, -}; - -#[test] -fn test_simple_vertex_traversal_v2() { - let g = traversal().with_remote(graph_serializer(GraphSON::V2)); - - let results = g.v(()).to_list().unwrap(); - - assert!(results.len() > 0); -} - -#[test] -fn test_simple_vertex_traversal_with_id_v2() { - let client = graph_serializer(GraphSON::V2); - - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); -} - -#[test] -fn test_simple_vertex_traversal_with_multiple_id_v2() { - let client = graph_serializer(GraphSON::V2); - drop_vertices(&client, "test_simple_vertex_traversal").unwrap(); - - let vertex = create_vertex_with_label(&client, "test_simple_vertex_traversal", "Traversal"); - let vertex2 = create_vertex_with_label(&client, "test_simple_vertex_traversal", "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vec![vertex.id(), vertex2.id()]).to_list().unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - assert_eq!(vertex2.id(), results[1].id()); -} - -#[test] -fn test_simple_vertex_traversal_with_label_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_simple_vertex_traversal_with_label").unwrap(); - - let vertex = create_vertex_with_label( - &client, - "test_simple_vertex_traversal_with_label", - "Traversal", - ); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_simple_vertex_traversal_with_label") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); -} - -#[test] -fn test_simple_vertex_traversal_with_label_and_has_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_simple_vertex_traversal_with_label_and_has").unwrap(); - - let vertex = create_vertex_with_label( - &client, - "test_simple_vertex_traversal_with_label_and_has", - "Traversal", - ); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_simple_vertex_traversal_with_label_and_has") - .has(("name", "Traversal")) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - - // with 3 params - - let results = g - .v(()) - .has(( - "test_simple_vertex_traversal_with_label_and_has", - "name", - "Traversal", - )) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - - // with 1 param - - let results = g - .v(()) - .has_label("test_simple_vertex_traversal_with_label_and_has") - .has("name") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); - - // hasNot - - let results = g - .v(()) - .has_label("test_simple_vertex_traversal_with_label_and_has") - .has_not("surname") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertex.id(), results[0].id()); -} - -#[test] -fn test_simple_edge_traversal_v2() { - let g = traversal().with_remote(graph_serializer(GraphSON::V2)); - - let results = g.e(()).to_list().unwrap(); - - assert!(results.len() > 0); -} - -#[test] -fn test_simple_edge_traversal_id_v2() { - let client = graph_serializer(GraphSON::V2); - - let v = create_vertex(&client, "Traversal"); - let v1 = create_vertex(&client, "Traversal"); - - let e = create_edge(&client, &v, &v1, "TraversalEdge"); - - let g = traversal().with_remote(client); - - let results = g.e(e.id()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(e.id(), results[0].id()); -} - -#[test] -fn test_simple_edge_traversal_with_label_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_edges(&client, "test_simple_edge_traversal_with_label").unwrap(); - - let v = create_vertex(&client, "Traversal"); - let v1 = create_vertex(&client, "Traversal"); - - let e = create_edge(&client, &v, &v1, "test_simple_edge_traversal_with_label"); - - let g = traversal().with_remote(client); - - let results = g - .e(()) - .has_label("test_simple_edge_traversal_with_label") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(e.id(), results[0].id()); -} - -#[test] -fn test_traversal_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_edges(&client, "test_vertex_out_traversal").unwrap(); - - let v = create_vertex(&client, "Traversal"); - let v1 = create_vertex(&client, "Traversal"); - let v2 = create_vertex(&client, "Traversal"); - - let _e = create_edge(&client, &v, &v1, "test_vertex_out_traversal"); - let _e2 = create_edge(&client, &v2, &v, "test_vertex_out_traversal"); - - let g = traversal().with_remote(client); - - // OUT - let results = g - .v(v.id()) - .out("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v1.id(), results[0].id()); - - let results = g.v(v.id()).out("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // OUT_E - - let results = g - .v(v.id()) - .out_e("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("test_vertex_out_traversal", results[0].label()); - - assert_eq!(v.id(), results[0].out_v().id()); - assert_eq!(v1.id(), results[0].in_v().id()); - - // OUT_E -> IN_V - let results = g - .v(v.id()) - .out_e("test_vertex_out_traversal") - .in_v() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v1.id(), results[0].id()); - - let results = g.v(v.id()).out("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // IN - let results = g - .v(v1.id()) - .in_("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v.id(), results[0].id()); - - let results = g.v(v1.id()).in_("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // IN_E - - let results = g - .v(v1.id()) - .in_e("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("test_vertex_out_traversal", results[0].label()); - - assert_eq!(v.id(), results[0].out_v().id()); - assert_eq!(v1.id(), results[0].in_v().id()); - - // IN_E -> OUT_V - let results = g - .v(v1.id()) - .in_e("test_vertex_out_traversal") - .out_v() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v.id(), results[0].id()); - - let results = g.v(v1.id()).in_("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // BOTH - - let results = g - .v(v.id()) - .both("test_vertex_out_traversal") - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!(v1.id(), results[0].id()); - assert_eq!(v2.id(), results[1].id()); - - let results = g.v(v1.id()).in_("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); - - // BOTH_E -> OTHER_V - - let results = g - .v(v.id()) - .both_e("test_vertex_out_traversal") - .other_v() - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!(v1.id(), results[0].id()); - assert_eq!(v2.id(), results[1].id()); - - let results = g.v(v1.id()).in_("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); -} - -#[test] -fn test_add_v_v2() { - let g = traversal().with_remote(graph_serializer(GraphSON::V2)); - - let results = g.add_v("person").to_list().unwrap(); - - assert!(results.len() > 0); - - assert_eq!("person", results[0].label()); - - let results = g.add_v("person").add_v(()).to_list().unwrap(); - - assert!(results.len() > 0); - - //default label - assert_eq!("vertex", results[0].label()); -} - -#[test] -fn test_add_v_with_properties_v2() { - let client = graph_serializer(GraphSON::V2); - let g = traversal().with_remote(client.clone()); - - let results = g - .add_v("person") - .property("name", "marko") - .property("age", 29) - .to_list() - .unwrap(); - - assert!(results.len() > 0); - - assert_eq!("person", results[0].label()); - - let results = client - .execute("g.V(_id).propertyMap()", &[("_id", results[0].id())]) - .expect("it should execute addV") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - let properties = &results[0]; - - assert_eq!( - &29, - properties["age"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &"marko", - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); -} - -#[test] -fn test_add_v_with_property_many_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_add_v_with_property_many").unwrap(); - - let g = traversal().with_remote(client.clone()); - - let results = g - .add_v("test_add_v_with_property_many") - .property_many(vec![ - (String::from("name"), "marko"), - (String::from("age"), "29"), - ]) - .to_list() - .unwrap(); - - assert!(results.len() > 0); - - assert_eq!("test_add_v_with_property_many", results[0].label()); - - let results = client - .execute("g.V(_id).propertyMap()", &[("_id", results[0].id())]) - .expect("it should execute addV") - .filter_map(Result::ok) - .map(|f| f.take::()) - .collect::, _>>() - .expect("It should be ok"); - - let properties = &results[0]; - - assert_eq!( - &"29".to_string(), - properties["age"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - assert_eq!( - &"marko", - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); -} - -#[test] -fn test_has_many_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_has_many").unwrap(); - - let g = traversal().with_remote(client.clone()); - - let results = g - .add_v("test_has_many") - .property_many(vec![ - (String::from("name"), "josh"), - (String::from("age"), "21"), - ]) - .to_list() - .unwrap(); - - assert!(results.len() > 0); - - assert_eq!("test_has_many", results[0].label()); - - let results = g - .v(()) - .has_many(vec![ - (String::from("name"), "josh"), - (String::from("age"), "21"), - ]) - .to_list() - .unwrap(); - - assert_eq!(results.len(), 1); -} - -#[test] -fn test_add_e_v2() { - let client = graph_serializer(GraphSON::V2); - let g = traversal().with_remote(client.clone()); - - let v = g - .add_v("person") - .property("name", "marko") - .property("age", 29) - .to_list() - .unwrap(); - - let v1 = g - .add_v("person") - .property("name", "jon") - .property("age", 29) - .to_list() - .unwrap(); - - let edges = g.add_e("knows").from(&v[0]).to(&v1[0]).to_list().unwrap(); - - assert!(edges.len() > 0); - - assert_eq!("knows", edges[0].label()); - - let edges = g - .v(v[0].id()) - .as_("a") - .out("knows") - .add_e("livesNear") - .from("a") - .property("year", 2009) - .to_list() - .unwrap(); - - assert!(edges.len() > 0); - - assert_eq!("livesNear", edges[0].label()); - - let edges = g - .v(()) - .as_("a") - .out("created") - .add_e("createdBy") - .to("a") - .property("acl", "public") - .to_list() - .unwrap(); - - assert_eq!("createdBy", edges[0].label()); - - let edges = g - .add_e("knows") - .from(__.v(()).has(("name", "marko"))) - .to(__.v(()).has(("name", "jon"))) - .to_list() - .unwrap(); - - assert!(edges.len() > 0); - - assert_eq!("knows", edges[0].label()); -} - -#[test] -fn test_label_step_v2() { - let client = graph_serializer(GraphSON::V2); - - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).label().to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("person", results[0]); -} - -#[test] -fn test_properties_step_v2() { - let client = graph_serializer(GraphSON::V2); - - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).properties(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("Traversal", results[0].get::().unwrap()); - - let results = g.v(vertex.id()).properties("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("Traversal", results[0].get::().unwrap()); - - let results = g.v(vertex.id()).properties("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); -} - -#[test] -fn test_property_map_v2() { - let client = graph_serializer(GraphSON::V2); - - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).property_map(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - let properties = &results[0]; - - assert_eq!( - "Traversal", - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - let results = g.v(vertex.id()).property_map("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let properties = &results[0]; - - assert_eq!( - "Traversal", - properties["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); - - let results = g.v(vertex.id()).property_map("fake").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let properties = &results[0]; - - assert_eq!(0, properties.len()); -} - -#[test] -fn test_values_v2() { - let client = graph_serializer(GraphSON::V2); - - let vertex = create_vertex(&client, "Traversal"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).values(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("Traversal", value.get::().unwrap()); - - let results = g.v(vertex.id()).values("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("Traversal", value.get::().unwrap()); - - let results = g.v(vertex.id()).values("fake").to_list().unwrap(); - - assert_eq!(0, results.len()); -} - -#[test] -fn test_value_map_v2() { - let client = graph_serializer(GraphSON::V2); - - let g = traversal().with_remote(client); - - let vertices = g - .add_v("test_value_map") - .property("name", "test") - .to_list() - .unwrap(); - - let vertex = &vertices[0]; - - let results = g.v(vertex.id()).value_map(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!( - "test", - value["name"].get::().unwrap()[0] - .get::() - .unwrap() - ); - - let results = g.v(vertex.id()).value_map("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!( - "test", - value["name"].get::().unwrap()[0] - .get::() - .unwrap() - ); - - let results = g.v(vertex.id()).value_map("fake").to_list().unwrap(); - - assert_eq!(0, results[0].len()); - - let results = g.v(vertex.id()).value_map(true).to_list().unwrap(); - - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); - assert_eq!(true, results[0].get("name").is_some()); -} - -#[test] -fn test_unwrap_map_v2() { - let client = graph_serializer(GraphSON::V2); - - let g = traversal().with_remote(client); - - let vertices = g - .add_v("test_value_map") - .property("name", "test") - .to_list() - .unwrap(); - - let vertex = &vertices[0]; - - let results = g.v(vertex.id()).value_map(true).next().unwrap().unwrap(); - let v_id = vertex.id().get::().unwrap(); - - let id = utils::unwrap_map::(&results, "id", 0); - let property = utils::unwrap_map::(&results, "name", 0); - let label = utils::unwrap_map::(&results, "label", 0); - - assert_eq!(id.is_ok(), true); - assert_eq!(property.is_ok(), true); - assert_eq!(label.is_ok(), true); - - assert_eq!(id.unwrap(), v_id); - assert_eq!(property.unwrap(), "test"); - assert_eq!(label.unwrap(), "test_value_map"); -} - -#[test] -fn test_element_map_v2() { - let client = graph_serializer(GraphSON::V2); - - let g = traversal().with_remote(client); - - let vertices = g - .add_v("test_element_map") - .property("name", "test") - .to_list() - .unwrap(); - - let vertex = &vertices[0]; - - let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("test", value["name"].get::().unwrap()); - - let results = g.v(vertex.id()).element_map("name").to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("test", value["name"].get::().unwrap()); - - let results = g.v(vertex.id()).element_map("fake").to_list().unwrap(); - - assert_eq!(2, results[0].len()); - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); - - let results = g.v(vertex.id()).element_map(()).to_list().unwrap(); - - assert_eq!(true, results[0].get("id").is_some()); - assert_eq!(true, results[0].get("label").is_some()); - assert_eq!(true, results[0].get("name").is_some()); -} - -#[test] -fn test_count_v2() { - let client = graph_serializer(GraphSON::V2); - - let vertex = create_vertex_with_label(&client, "test_count", "Count"); - - let g = traversal().with_remote(client); - - let results = g.v(vertex.id()).count().to_list().unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value); -} - -#[test] -fn test_group_count_step_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_group_count").unwrap(); - - let vertex = create_vertex_with_label(&client, "test_group_count", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_group_count") - .group_count() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!( - &GValue::Int64(1), - value - .get(GKey::String(match vertex.id() { - GID::String(s) => s.to_string(), - GID::Int32(i) => i.to_string(), - GID::Int64(i) => i.to_string(), - })) - .unwrap() - ); - //println - //value[&vertex].get::().unwrap()); - - let results = g - .v(()) - .has_label("test_group_count") - .group_count() - .by("name") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value["Count"].get::().unwrap()); - - let results = g - .v(()) - .has_label("test_group_count") - .group_count() - .by(T::Label) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value["test_group_count"].get::().unwrap()); -} - -#[test] -fn test_group_by_step_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_group_by_step").unwrap(); - - create_vertex_with_label(&client, "test_group_by_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_group_by_step") - .group() - .by("name") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(1, value["Count"].get::().unwrap().len()); - - let results = g - .v(()) - .has_label("test_group_by_step") - .group() - .by(T::Label) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(1, value["test_group_by_step"].get::().unwrap().len()); - - // - - let results = g - .v(()) - .has_label("test_group_by_step") - .group() - .by(T::Label) - .by(__.count()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value["test_group_by_step"].get::().unwrap()); -} - -#[test] -fn test_select_step_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_select_step").unwrap(); - - create_vertex_with_label(&client, "test_select_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_select_step") - .group_count() - .by("name") - .select("Count") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(&1, value.get::().unwrap()); -} - -#[test] -fn test_fold_step_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_fold_step").unwrap(); - - create_vertex_with_label(&client, "test_fold_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_fold_step") - .values("name") - .fold() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!("Count", value[0].get::().unwrap()); -} - -#[test] -fn test_unfold_step_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_unfold_step").unwrap(); - - let vertex = create_vertex_with_label(&client, "test_unfold_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(vertex.id()) - .property_map(()) - .unfold() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!( - "Count", - value["name"].get::().unwrap()[0] - .get::() - .unwrap() - .get::() - .unwrap() - ); -} - -#[test] -fn test_path_step_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_path_step").unwrap(); - - let v = create_vertex_with_label(&client, "test_path_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_path_step") - .path() - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let value = &results[0]; - - assert_eq!(v.id(), value.objects()[0].get::().unwrap().id()); -} - -#[test] -fn test_limit_step_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_limit_step").unwrap(); - - create_vertex_with_label(&client, "test_limit_step", "Count"); - create_vertex_with_label(&client, "test_limit_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_limit_step") - .limit(1) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); -} - -#[test] -fn test_dedup_step_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_limit_step").unwrap(); - - create_vertex_with_label(&client, "test_limit_step", "Count"); - create_vertex_with_label(&client, "test_limit_step", "Count"); - - let g = traversal().with_remote(client); - - let results = g - .v(()) - .has_label("test_limit_step") - .dedup(()) - .by(T::Label) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); -} - -#[test] -fn test_numerical_steps_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_numerical_steps").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("test_numerical_steps") - .property("age", 26) - .to_list() - .unwrap(); - g.add_v("test_numerical_steps") - .property("age", 20) - .to_list() - .unwrap(); - - // sum - let results = g - .v(()) - .has_label("test_numerical_steps") - .values("age") - .sum(()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&46, results[0].get::().unwrap()); - - // max - let results = g - .v(()) - .has_label("test_numerical_steps") - .values("age") - .max(()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&26, results[0].get::().unwrap()); - - // mean - - let results = g - .v(()) - .has_label("test_numerical_steps") - .values("age") - .mean(()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&23.0, results[0].get::().unwrap()); - - // min - - let results = g - .v(()) - .has_label("test_numerical_steps") - .values("age") - .min(()) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&20, results[0].get::().unwrap()); -} - -#[test] -fn test_has_with_p_steps_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_has_with_p_steps").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("test_has_with_p_steps") - .property("age", 26) - .to_list() - .unwrap(); - let vertices = g - .add_v("test_has_with_p_steps") - .property("age", 20) - .to_list() - .unwrap(); - - let results = g - .v(()) - .has(("test_has_with_p_steps", "age", P::within(vec![19, 20]))) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertices[0].id(), results[0].id()); - - let results = g - .v(()) - .has_label("test_has_with_p_steps") - .values("age") - .is(20) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&20, results[0].get::().unwrap()); - - let results = g - .v(()) - .has_label("test_has_with_p_steps") - .values("age") - .is(P::within(vec![19, 20])) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(&20, results[0].get::().unwrap()); -} - -#[test] -fn test_has_with_text_p_step_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_has_with_text_p_step").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("test_has_with_text_p_step") - .property("name", "Jon") - .to_list() - .unwrap(); - - let vertices = g - .add_v("test_has_with_text_p_step") - .property("name", "Alice") - .to_list() - .unwrap(); - - let results = g - .v(()) - .has(("test_has_with_text_p_step", "name", TextP::containing("A"))) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(vertices[0].id(), results[0].id()); - - let results = g - .v(()) - .has_label("test_has_with_text_p_step") - .values("name") - .is("Alice") - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("Alice", results[0].get::().unwrap()); - - let results = g - .v(()) - .has_label("test_has_with_text_p_step") - .values("name") - .is(TextP::containing("Al")) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!("Alice", results[0].get::().unwrap()); - - g.add_v("test_has_with_text_p_step") - .property("name", "Alice2") - .to_list() - .unwrap(); - - let results = g - .v(()) - .has(("test_has_with_text_p_step", "name", TextP::containing("A"))) - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); -} - -#[test] -fn where_step_test_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "where_step_test").unwrap(); - - let g = traversal().with_remote(client); - - let v = g - .add_v("where_step_test") - .property("age", 26) - .to_list() - .unwrap(); - - let results = g - .v(()) - .has_label("where_step_test") - .where_(__.values("age").is(26)) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - assert_eq!(v[0].id(), results[0].id()); -} - -#[test] -fn not_step_test_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "not_step_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("not_step_test") - .property("age", 26) - .to_list() - .unwrap(); - - let results = g - .v(()) - .has_label("not_step_test") - .not(__.values("age").is(26)) - .to_list() - .unwrap(); - - assert_eq!(0, results.len()); -} - -#[test] -fn order_step_test_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "order_step_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("order_step_test") - .property("name", "b") - .to_list() - .unwrap(); - - g.add_v("order_step_test") - .property("name", "a") - .to_list() - .unwrap(); - - let results = g - .v(()) - .has_label("order_step_test") - .values("name") - .order(()) - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!("a", results[0].get::().unwrap()); - - let results = g - .v(()) - .has_label("order_step_test") - .values("name") - .order(()) - .by(Order::Desc) - .to_list() - .unwrap(); - - assert_eq!(2, results.len()); - - assert_eq!("b", results[0].get::().unwrap()); -} - -#[test] -fn match_step_test_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "match_step_test").unwrap(); - - drop_edges(&client, "match_step_test_edge").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("match_step_test") - .property("name", "a") - .to_list() - .unwrap(); - - let v2 = g - .add_v("match_step_test") - .property("name", "b") - .to_list() - .unwrap(); - - let v3 = g - .add_v("match_step_test") - .property("name", "c") - .to_list() - .unwrap(); - - g.add_e("match_step_test_edge") - .from(&v1[0]) - .to(&v2[0]) - .to_list() - .unwrap(); - - g.add_e("match_step_test_edge") - .from(&v2[0]) - .to(&v3[0]) - .to_list() - .unwrap(); - - let results = g - .v(()) - .has_label("match_step_test") - .match_(vec![ - __.as_("a") - .has(("name", "a")) - .out("match_step_test_edge") - .as_("b"), - __.as_("b").out("match_step_test_edge").as_("c"), - ]) - .select(vec!["a", "c"]) - .to_list() - .unwrap(); - - assert_eq!(1, results.len()); - - let first = &results[0].get::().unwrap(); - - assert_eq!(&v1[0], first["a"].get::().unwrap()); - assert_eq!(&v3[0], first["c"].get::().unwrap()); -} - -#[test] -fn drop_step_test_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "drop_step_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("drop_step_test") - .property("name", "a") - .next() - .unwrap(); - - g.add_v("drop_step_test") - .property("name", "b") - .next() - .unwrap(); - - let results = g.v(()).has_label("drop_step_test").count().next().unwrap(); - - assert_eq!(Some(2), results); - - g.v(()) - .has_label("drop_step_test") - .drop() - .to_list() - .unwrap(); - - let results = g.v(()).has_label("drop_step_test").has_next().unwrap(); - - assert_eq!(false, results); -} - -#[test] -fn or_step_test_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "or_step_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("or_step_test") - .property("foo", "bar") - .property("bar", "foo") - .next() - .unwrap(); - - g.add_v("or_step_test") - .property("foo", "nobar") - .property("bar", "nofoo") - .next() - .unwrap(); - - let result = g - .v(()) - .has_label("or_step_test") - .has(("foo", "bar")) - .or(()) - .has(("bar", "foo")) - .to_list() - .unwrap(); - assert_eq!(result.len(), 1); - - let result = g - .v(()) - .has_label("or_step_test") - .has(("foo", "bar")) - .or(()) - .has(("bar", "nofoo")) - .to_list() - .unwrap(); - assert_eq!(result.len(), 2); -} - -#[test] -fn iter_terminator_test_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "iter_terminator_test").unwrap(); - - let g = traversal().with_remote(client); - - g.add_v("iter_terminator_test") - .property("name", "a") - .next() - .unwrap(); - - g.add_v("iter_terminator_test") - .property("name", "b") - .next() - .unwrap(); - - let results: Vec = g - .v(()) - .has_label("iter_terminator_test") - .iter() - .unwrap() - .filter_map(Result::ok) - .collect(); - - assert_eq!(2, results.len()) -} - -#[test] -fn test_select_pop_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_select_pop").unwrap(); - drop_vertices(&client, "test_select_pop_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_select_pop") - .property("name", "a") - .to_list() - .unwrap(); - - let v2 = g - .add_v("test_select_pop") - .property("name", "b") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_select_pop_child") - .property("name", "a") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_select_pop_child") - .property("name", "b") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - - g.add_e("child").from(&v2[0]).to(&e2[0]).to_list().unwrap(); - - let results = g - .v(()) - .has_label("test_select_pop") - .has(("name", "a")) - .out("child") - .as_("v") - .v(()) - .has_label("test_select_pop") - .has(("name", "b")) - .out("child") - .as_("v") - .select((Pop::All, "v")) - .unfold() - .to_list() - .unwrap(); - assert_eq!(results.len(), 2); - - let results = g - .v(()) - .has_label("test_select_pop") - .has(("name", "a")) - .out("child") - .as_("v") - .v(()) - .has_label("test_select_pop") - .has(("name", "b")) - .out("child") - .as_("v") - .select((Pop::Last, "v")) - .unfold() - .to_list() - .unwrap(); - assert_eq!(results.len(), 1); - - let results = g - .v(()) - .has_label("test_select_pop") - .has(("name", "a")) - .out("child") - .as_("v") - .v(()) - .has_label("test_select_pop") - .has(("name", "b")) - .out("child") - .as_("v") - .select((Pop::First, "v")) - .unfold() - .to_list() - .unwrap(); - assert_eq!(results.len(), 1); -} - -#[test] -fn test_repeat_until_loops_loops_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_repeat_until_loops").unwrap(); - drop_vertices(&client, "test_repeat_until_loops_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_repeat_until_loops") - .property("name", "a") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_repeat_until_loops_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_repeat_until_loops_child") - .property("name", "c") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - g.add_e("child").from(&e1[0]).to(&e2[0]).to_list().unwrap(); - - let results = g - .v(v1[0].id()) - .repeat(__.out("child")) - .until(__.loops(()).is(2)) - .to_list() - .unwrap(); - - assert_eq!(results.len(), 1); - assert_eq!(results[0], e2[0]); -} - -#[test] -fn test_simple_path_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_simple_path").unwrap(); - drop_vertices(&client, "test_simple_path_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_simple_path") - .property("name", "a") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_simple_path_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_simple_path_child") - .property("name", "c") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - g.add_e("child").from(&e1[0]).to(&e2[0]).to_list().unwrap(); - g.add_e("child").from(&e2[0]).to(&v1[0]).to_list().unwrap(); - - let results = g - .v(v1[0].id()) - .repeat(__.out("child").simple_path()) - .until(__.loops(()).is(2)) - .to_list() - .unwrap(); - - assert_eq!(results.len(), 1); - assert_eq!(results[0], e2[0]); -} - -#[test] -fn test_sample_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_sample").unwrap(); - drop_vertices(&client, "test_sample_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_sample") - .property("name", "a") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_sample_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_sample_child") - .property("name", "b") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - g.add_e("child").from(&v1[0]).to(&e2[0]).to_list().unwrap(); - let results = g.v(v1[0].id()).out("child").sample(1).to_list().unwrap(); - assert_eq!(results.len(), 1); -} - -#[test] -fn test_local_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_local").unwrap(); - drop_vertices(&client, "test_local_child").unwrap(); - drop_vertices(&client, "test_local_child_child").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_local") - .property("name", "a") - .to_list() - .unwrap(); - - let e1 = g - .add_v("test_local_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e2 = g - .add_v("test_local_child") - .property("name", "b") - .to_list() - .unwrap(); - - let e3 = g - .add_v("test_local_child_child") - .property("name", "c") - .to_list() - .unwrap(); - - let e4 = g - .add_v("test_local_child_child") - .property("name", "d") - .to_list() - .unwrap(); - - let e5 = g - .add_v("test_local_child_child") - .property("name", "e") - .to_list() - .unwrap(); - - g.add_e("child").from(&v1[0]).to(&e1[0]).to_list().unwrap(); - g.add_e("child").from(&v1[0]).to(&e2[0]).to_list().unwrap(); - - g.add_e("child_child") - .from(&e1[0]) - .to(&e3[0]) - .to_list() - .unwrap(); - g.add_e("child_child") - .from(&e1[0]) - .to(&e4[0]) - .to_list() - .unwrap(); - - g.add_e("child_child") - .from(&e2[0]) - .to(&e5[0]) - .to_list() - .unwrap(); - - let results = g - .v(v1[0].id()) - .out("child") - .local(__.out("child_child").sample(1)) //Local used here to only get one vertices from each child - .to_list() - .unwrap(); - - assert_eq!(results.len(), 2); -} - -#[test] -fn test_property_cardinality_v2() { - let client = graph_serializer(GraphSON::V2); - - drop_vertices(&client, "test_property_cardinality").unwrap(); - - let g = traversal().with_remote(client); - - let v1 = g - .add_v("test_property_cardinality") - .property("name", "a") - .to_list() - .unwrap(); - - assert!(v1.len() > 0); - - g.v(v1[0].id()) - .property_with_cardinality(Cardinality::List, "name", "b") - .next() - .unwrap(); - let new_v = g.v(v1[0].id()).property_map(()).next().unwrap().unwrap(); - assert_eq!(2, new_v["name"].get::().unwrap().len()); - - g.v(v1[0].id()) - .property_with_cardinality(Cardinality::Single, "name", "b") - .next() - .unwrap(); - let new_v = g.v(v1[0].id()).property_map(()).next().unwrap().unwrap(); - assert_eq!(1, new_v["name"].get::().unwrap().len()); -}