use std::collections::BTreeMap;
use serde::{Deserialize, Serialize};
use serde_json::{json, Map, Value};
use crate::client::Client;
use crate::error::{Error, Result};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum GraphDirection {
#[default]
Outgoing,
Incoming,
Both,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphNode {
pub id: String,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub labels: Vec<String>,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub properties: Map<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphEdge {
pub id: String,
#[serde(default)]
pub from: String,
#[serde(default)]
pub to: String,
#[serde(default)]
pub label: String,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub properties: Map<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphNeighbor {
pub edge_id: String,
pub node_id: String,
#[serde(default)]
pub label: String,
#[serde(default)]
pub direction: GraphDirection,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphVisit {
pub id: String,
#[serde(default)]
pub depth: usize,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub labels: Vec<String>,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub properties: Map<String, Value>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphTraversal {
#[serde(default)]
pub start: String,
#[serde(default)]
pub direction: String,
#[serde(default)]
pub max_depth: usize,
#[serde(default)]
pub limit: usize,
#[serde(default)]
pub count: usize,
#[serde(default)]
pub truncated: bool,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub nodes: Vec<GraphVisit>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphPath {
#[serde(default)]
pub found: bool,
#[serde(default)]
pub from: String,
#[serde(default)]
pub to: String,
#[serde(default)]
pub direction: String,
#[serde(default)]
pub hops: usize,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub node_path: Vec<String>,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub edge_path: Vec<String>,
#[serde(default)]
pub total_cost: f64,
#[serde(default)]
pub message: String,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphNodePage {
#[serde(default)]
pub graph: String,
#[serde(default)]
pub count: usize,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub nodes: Vec<GraphNode>,
#[serde(default)]
pub truncated: bool,
#[serde(default)]
pub total: i64,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphEdgePage {
#[serde(default)]
pub graph: String,
#[serde(default)]
pub count: usize,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub edges: Vec<GraphEdge>,
#[serde(default)]
pub truncated: bool,
#[serde(default)]
pub total: i64,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphRows {
#[serde(default)]
pub graph: String,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub columns: Vec<String>,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub rows: Vec<Vec<Value>>,
#[serde(default)]
pub count: usize,
#[serde(default)]
pub truncated: bool,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct NeighborOptions {
pub direction: Option<GraphDirection>,
pub label: Option<String>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct TraverseOptions {
pub direction: Option<GraphDirection>,
pub label: Option<String>,
pub max_depth: Option<usize>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct PathOptions {
pub direction: Option<GraphDirection>,
pub label: Option<String>,
pub max_depth: Option<usize>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct WeightedPathOptions {
pub direction: Option<GraphDirection>,
pub label: Option<String>,
pub weight_property: Option<String>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ComponentKind {
#[default]
Weak,
Strong,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConstraintKind {
Unique,
Required,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum ConstraintTarget {
#[default]
Node,
Edge,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum CentralityMeasure {
Betweenness,
Closeness,
Eigenvector,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum SimilarityMetric {
#[default]
Jaccard,
Overlap,
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct GraphNodeInput {
pub id: String,
pub labels: Vec<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<Map<String, Value>>,
}
impl GraphNodeInput {
pub fn new(id: &str, labels: &[&str]) -> Self {
Self {
id: id.to_string(),
labels: labels.iter().map(|l| l.to_string()).collect(),
properties: None,
}
}
pub fn properties(mut self, properties: Map<String, Value>) -> Self {
self.properties = Some(properties);
self
}
}
#[derive(Debug, Clone, PartialEq, Serialize)]
pub struct GraphEdgeInput {
pub id: String,
pub from: String,
pub to: String,
pub label: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub properties: Option<Map<String, Value>>,
}
impl GraphEdgeInput {
pub fn new(id: &str, from: &str, to: &str, label: &str) -> Self {
Self {
id: id.to_string(),
from: from.to_string(),
to: to.to_string(),
label: label.to_string(),
properties: None,
}
}
pub fn properties(mut self, properties: Map<String, Value>) -> Self {
self.properties = Some(properties);
self
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
pub struct GraphCounts {
#[serde(default)]
pub nodes: i64,
#[serde(default)]
pub edges: i64,
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Deserialize)]
pub struct GraphRemoved {
#[serde(default)]
pub nodes_removed: i64,
#[serde(default)]
pub edges_removed: i64,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct NodeUpdate {
pub properties: Option<Value>,
pub add_labels: Vec<String>,
pub remove_labels: Vec<String>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ComponentOptions {
pub kind: Option<ComponentKind>,
pub label: Option<String>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct GraphGroup {
#[serde(default)]
pub nodes: Vec<String>,
#[serde(default)]
pub size: usize,
#[serde(default)]
pub truncated: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct GraphComponents {
#[serde(default)]
pub kind: ComponentKind,
#[serde(default)]
pub count: usize,
#[serde(default)]
pub largest: usize,
#[serde(default)]
pub truncated: bool,
#[serde(default)]
pub components: Vec<GraphGroup>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct GraphCycle {
#[serde(default)]
pub nodes: Vec<String>,
#[serde(default)]
pub edges: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct GraphCycleCheck {
#[serde(default, rename = "hasCycle")]
pub has_cycle: bool,
#[serde(default)]
pub directed: bool,
#[serde(default)]
pub cycle: Option<GraphCycle>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GraphStats {
#[serde(default)]
pub nodes: i64,
#[serde(default)]
pub edges: i64,
#[serde(default)]
pub node_labels: BTreeMap<String, i64>,
#[serde(default)]
pub edge_types: BTreeMap<String, i64>,
#[serde(default)]
pub unlabeled_nodes: i64,
#[serde(default)]
pub self_loops: i64,
#[serde(default)]
pub isolated_nodes: i64,
#[serde(default)]
pub avg_out_degree: f64,
#[serde(default)]
pub max_out_degree: i64,
#[serde(default)]
pub max_in_degree: i64,
#[serde(default)]
pub dangling_edges: i64,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphSubgraph {
#[serde(default)]
pub direction: String,
#[serde(default)]
pub max_depth: usize,
#[serde(default)]
pub limit: usize,
#[serde(default)]
pub node_count: usize,
#[serde(default)]
pub edge_count: usize,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub nodes: Vec<GraphVisit>,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub edges: Vec<GraphEdge>,
#[serde(default)]
pub nodes_truncated: bool,
#[serde(default)]
pub edges_truncated: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct GraphPropertySchema {
pub key: String,
#[serde(default)]
pub count: i64,
#[serde(default)]
pub types: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
pub struct GraphLabelSchema {
#[serde(default)]
pub label: String,
#[serde(default)]
pub count: i64,
#[serde(default)]
pub properties: Vec<GraphPropertySchema>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct GraphEdgeTypeSchema {
#[serde(rename = "type")]
pub edge_type: String,
#[serde(default)]
pub count: i64,
#[serde(default)]
pub properties: Vec<GraphPropertySchema>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct GraphSchema {
#[serde(default)]
pub nodes: i64,
#[serde(default)]
pub edges: i64,
#[serde(default)]
pub node_labels: Vec<GraphLabelSchema>,
#[serde(default)]
pub edge_types: Vec<GraphEdgeTypeSchema>,
#[serde(default)]
pub unlabeled_nodes: GraphLabelSchema,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct KPathOptions {
pub k: Option<usize>,
pub direction: Option<GraphDirection>,
pub label: Option<String>,
pub weight_property: Option<String>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphPathEntry {
#[serde(default)]
pub nodes: Vec<String>,
#[serde(default)]
pub edges: Vec<String>,
#[serde(default)]
pub hops: usize,
#[serde(default)]
pub cost: f64,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphPaths {
#[serde(default)]
pub from: String,
#[serde(default)]
pub to: String,
#[serde(default)]
pub direction: String,
#[serde(default)]
pub count: usize,
#[serde(default)]
pub paths: Vec<GraphPathEntry>,
#[serde(default)]
pub complete: Option<bool>,
#[serde(default)]
pub stopped_by: Option<String>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct CentralityOptions {
pub directed: Option<bool>,
pub label: Option<String>,
pub limit: Option<usize>,
pub iterations: Option<usize>,
pub tolerance: Option<f64>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphScore {
pub id: String,
#[serde(default)]
pub score: f64,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphCentrality {
pub measure: CentralityMeasure,
#[serde(default)]
pub directed: bool,
#[serde(default)]
pub nodes: i64,
#[serde(default)]
pub results: Vec<GraphScore>,
#[serde(default)]
pub converged: Option<bool>,
#[serde(default)]
pub iterations: Option<usize>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct CommunityOptions {
pub label: Option<String>,
pub iterations: Option<usize>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphCommunities {
#[serde(default)]
pub algorithm: String,
#[serde(default)]
pub count: usize,
#[serde(default)]
pub modularity: f64,
#[serde(default)]
pub converged: bool,
#[serde(default)]
pub iterations: usize,
#[serde(default)]
pub communities: Vec<GraphGroup>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphNodeTriangles {
pub id: String,
#[serde(default)]
pub triangles: i64,
#[serde(default)]
pub clustering: f64,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphTriangles {
#[serde(default)]
pub triangles: i64,
#[serde(default)]
pub transitivity: f64,
#[serde(default)]
pub average_clustering: f64,
#[serde(default)]
pub nodes: Vec<GraphNodeTriangles>,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct SimilarityOptions {
pub direction: Option<GraphDirection>,
pub label: Option<String>,
pub metric: Option<SimilarityMetric>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize)]
pub struct GraphFindings {
#[serde(default)]
pub count: i64,
#[serde(default)]
pub listed: Vec<Value>,
}
#[derive(Debug, Clone, PartialEq, Default, Deserialize)]
pub struct GraphMirrorCheck {
#[serde(default)]
pub loaded: bool,
#[serde(default)]
pub differences: GraphFindings,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphConsistency {
#[serde(default)]
pub consistent: bool,
#[serde(default)]
pub nodes: i64,
#[serde(default)]
pub edges: i64,
#[serde(default)]
pub dangling_edges: GraphFindings,
#[serde(default)]
pub undecodable: GraphFindings,
#[serde(default)]
pub misfiled: GraphFindings,
#[serde(default)]
pub mirror: GraphMirrorCheck,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct GraphIndex {
pub name: String,
#[serde(default)]
pub label: Option<String>,
#[serde(default)]
pub properties: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, Deserialize)]
pub struct GraphConstraint {
pub name: String,
#[serde(default)]
pub label: String,
#[serde(default)]
pub property: String,
pub kind: ConstraintKind,
#[serde(default)]
pub target: ConstraintTarget,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
pub struct GraphSchemaObjects {
#[serde(default)]
pub indexes: Vec<GraphIndex>,
#[serde(default)]
pub constraints: Vec<GraphConstraint>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphFoundNodes {
#[serde(default)]
pub count: usize,
#[serde(default, deserialize_with = "crate::serde_null::or_default")]
pub nodes: Vec<GraphNode>,
#[serde(default)]
pub plan: String,
#[serde(default)]
pub truncated: bool,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PageRankOptions {
pub damping: Option<f64>,
pub iterations: Option<usize>,
pub tolerance: Option<f64>,
pub label: Option<String>,
pub limit: Option<usize>,
}
#[derive(Debug, Clone, PartialEq, Deserialize)]
pub struct GraphPageRank {
#[serde(default)]
pub ranks: Vec<GraphScore>,
#[serde(default)]
pub count: usize,
#[serde(default)]
pub damping: f64,
#[serde(default)]
pub iterations: usize,
#[serde(default)]
pub converged: bool,
#[serde(default)]
pub truncated: bool,
}
#[derive(Debug, Clone, PartialEq, Eq, Default, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct GraphWriteSummary {
#[serde(default)]
pub statements: i64,
#[serde(default)]
pub nodes_created: i64,
#[serde(default)]
pub nodes_deleted: i64,
#[serde(default)]
pub relationships_created: i64,
#[serde(default)]
pub relationships_deleted: i64,
#[serde(default)]
pub properties_set: i64,
#[serde(default)]
pub labels_added: i64,
#[serde(default)]
pub labels_removed: i64,
#[serde(default)]
pub queued: Option<i64>,
#[serde(default)]
pub committed: Option<i64>,
}
fn op(body: Value) -> Value {
json!({"Graph": body})
}
fn put(body: &mut Map<String, Value>, key: &str, value: Option<impl Into<Value>>) {
if let Some(value) = value {
body.insert(key.to_string(), value.into());
}
}
fn direction_value(direction: Option<GraphDirection>) -> Option<Value> {
direction.map(|d| serde_json::to_value(d).expect("a direction always encodes"))
}
impl Client {
pub fn graph_create(&mut self, graph: &str) -> Result<()> {
self.send(op(json!({"CreateGraph": {"graph": graph}})))
.map(|_| ())
}
pub fn graph_drop(&mut self, graph: &str) -> Result<()> {
self.send(op(json!({"DropGraph": {"graph": graph}})))
.map(|_| ())
}
pub fn graph_list(&mut self) -> Result<Vec<String>> {
#[derive(Deserialize)]
struct Graphs {
#[serde(default)]
graphs: Vec<String>,
}
let response = self.send(op(Value::String("ListGraphs".into())))?;
let out: Graphs = response.decode("ListGraphs")?;
Ok(out.graphs)
}
pub fn graph_add_node(
&mut self,
graph: &str,
id: &str,
labels: &[&str],
properties: Option<&Map<String, Value>>,
) -> Result<()> {
self.send(op(json!({"AddNode": {
"graph": graph,
"id": id,
"labels": labels,
"properties": properties,
}})))
.map(|_| ())
}
pub fn graph_get_node(&mut self, graph: &str, id: &str) -> Result<Option<GraphNode>> {
let response = self.send(op(json!({"GetNode": {"graph": graph, "id": id}})))?;
if response.expect("Json", "GetNode")?.is_null() {
return Ok(None);
}
response.decode("GetNode").map(Some)
}
pub fn graph_delete_node(&mut self, graph: &str, id: &str) -> Result<()> {
self.send(op(json!({"DeleteNode": {"graph": graph, "id": id}})))
.map(|_| ())
}
pub fn graph_list_nodes(
&mut self,
graph: &str,
limit: Option<usize>,
offset: Option<usize>,
) -> Result<GraphNodePage> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
put(&mut body, "limit", limit.filter(|l| *l > 0));
put(&mut body, "offset", offset.filter(|o| *o > 0));
let response = self.send(op(json!({"ListNodes": Value::Object(body)})))?;
response.decode("ListNodes")
}
pub fn graph_add_edge(
&mut self,
graph: &str,
id: &str,
from: &str,
to: &str,
label: &str,
properties: Option<&Map<String, Value>>,
) -> Result<()> {
self.send(op(json!({"AddEdge": {
"graph": graph,
"id": id,
"from": from,
"to": to,
"label": label,
"properties": properties,
}})))
.map(|_| ())
}
pub fn graph_get_edge(&mut self, graph: &str, id: &str) -> Result<Option<GraphEdge>> {
let response = self.send(op(json!({"GetEdge": {"graph": graph, "id": id}})))?;
if response.expect("Json", "GetEdge")?.is_null() {
return Ok(None);
}
response.decode("GetEdge").map(Some)
}
pub fn graph_delete_edge(&mut self, graph: &str, id: &str) -> Result<()> {
self.send(op(json!({"DeleteEdge": {"graph": graph, "id": id}})))
.map(|_| ())
}
pub fn graph_list_edges(
&mut self,
graph: &str,
limit: Option<usize>,
offset: Option<usize>,
) -> Result<GraphEdgePage> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
put(&mut body, "limit", limit.filter(|l| *l > 0));
put(&mut body, "offset", offset.filter(|o| *o > 0));
let response = self.send(op(json!({"ListEdges": Value::Object(body)})))?;
response.decode("ListEdges")
}
pub fn graph_neighbors(
&mut self,
graph: &str,
node_id: &str,
options: &NeighborOptions,
) -> Result<Vec<GraphNeighbor>> {
#[derive(Deserialize)]
struct Neighbors {
#[serde(default)]
neighbors: Vec<GraphNeighbor>,
}
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("node_id".into(), json!(node_id));
put(&mut body, "direction", direction_value(options.direction));
put(&mut body, "label", options.label.clone());
put(&mut body, "limit", options.limit.filter(|l| *l > 0));
let response = self.send(op(json!({"Neighbors": Value::Object(body)})))?;
let out: Neighbors = response.decode("Neighbors")?;
Ok(out.neighbors)
}
pub fn graph_degree(
&mut self,
graph: &str,
node_id: &str,
direction: Option<GraphDirection>,
) -> Result<i64> {
#[derive(Deserialize)]
struct Degree {
#[serde(default)]
degree: i64,
}
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("node_id".into(), json!(node_id));
put(&mut body, "direction", direction_value(direction));
let response = self.send(op(json!({"Degree": Value::Object(body)})))?;
let out: Degree = response.decode("Degree")?;
Ok(out.degree)
}
pub fn graph_traverse(
&mut self,
graph: &str,
start: &str,
options: &TraverseOptions,
) -> Result<GraphTraversal> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("start".into(), json!(start));
put(&mut body, "direction", direction_value(options.direction));
put(&mut body, "label", options.label.clone());
put(&mut body, "max_depth", options.max_depth.filter(|d| *d > 0));
put(&mut body, "limit", options.limit.filter(|l| *l > 0));
let response = self.send(op(json!({"Traverse": Value::Object(body)})))?;
response.decode("Traverse")
}
pub fn graph_shortest_path(
&mut self,
graph: &str,
from: &str,
to: &str,
options: &PathOptions,
) -> Result<GraphPath> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("from".into(), json!(from));
body.insert("to".into(), json!(to));
put(&mut body, "direction", direction_value(options.direction));
put(&mut body, "label", options.label.clone());
put(&mut body, "max_depth", options.max_depth.filter(|d| *d > 0));
let response = self.send(op(json!({"ShortestPath": Value::Object(body)})))?;
response.decode("ShortestPath")
}
pub fn graph_weighted_shortest_path(
&mut self,
graph: &str,
from: &str,
to: &str,
options: &WeightedPathOptions,
) -> Result<GraphPath> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("from".into(), json!(from));
body.insert("to".into(), json!(to));
put(&mut body, "direction", direction_value(options.direction));
put(&mut body, "label", options.label.clone());
put(
&mut body,
"weight_property",
options.weight_property.clone(),
);
let response = self.send(op(json!({"WeightedShortestPath": Value::Object(body)})))?;
response.decode("WeightedShortestPath")
}
pub fn graph_query(&mut self, graph: &str, cypher: &str) -> Result<GraphRows> {
let response = self.send(op(json!({"Query": {"graph": graph, "cypher": cypher}})))?;
response.decode("Query")
}
pub fn graph_add_many(
&mut self,
graph: &str,
nodes: &[GraphNodeInput],
edges: &[GraphEdgeInput],
) -> Result<GraphCounts> {
let response = self.send(op(json!({"AddMany": {
"graph": graph,
"nodes": nodes,
"edges": edges,
}})))?;
response.decode("AddMany")
}
pub fn graph_remove_many(
&mut self,
graph: &str,
nodes: &[&str],
edges: &[&str],
detach: bool,
) -> Result<GraphRemoved> {
let response = self.send(op(json!({"RemoveMany": {
"graph": graph,
"nodes": nodes,
"edges": edges,
"detach": detach,
}})))?;
response.decode("RemoveMany")
}
pub fn graph_update_node(
&mut self,
graph: &str,
id: &str,
update: &NodeUpdate,
) -> Result<GraphNode> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("id".into(), json!(id));
put(&mut body, "properties", update.properties.clone());
if !update.add_labels.is_empty() {
body.insert("add_labels".into(), json!(update.add_labels));
}
if !update.remove_labels.is_empty() {
body.insert("remove_labels".into(), json!(update.remove_labels));
}
let response = self.send(op(json!({"UpdateNode": Value::Object(body)})))?;
response.decode("UpdateNode")
}
pub fn graph_update_edge(
&mut self,
graph: &str,
id: &str,
properties: Option<&Value>,
label: Option<&str>,
) -> Result<GraphEdge> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("id".into(), json!(id));
put(&mut body, "properties", properties.cloned());
put(&mut body, "label", label);
let response = self.send(op(json!({"UpdateEdge": Value::Object(body)})))?;
response.decode("UpdateEdge")
}
pub fn graph_copy(&mut self, graph: &str, to: &str) -> Result<GraphCounts> {
let response = self.send(op(json!({"CopyGraph": {"graph": graph, "to": to}})))?;
response.decode("CopyGraph")
}
pub fn graph_dfs(
&mut self,
graph: &str,
start: &str,
options: &TraverseOptions,
) -> Result<GraphTraversal> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("start".into(), json!(start));
put_walk(&mut body, options);
let response = self.send(op(json!({"Dfs": Value::Object(body)})))?;
response.decode("Dfs")
}
pub fn graph_connected_components(
&mut self,
graph: &str,
options: &ComponentOptions,
) -> Result<GraphComponents> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
put(&mut body, "kind", options.kind.map(|k| json!(k)));
put(&mut body, "label", options.label.clone());
put(&mut body, "limit", options.limit);
let response = self.send(op(json!({"ConnectedComponents": Value::Object(body)})))?;
response.decode("ConnectedComponents")
}
pub fn graph_detect_cycle(
&mut self,
graph: &str,
directed: Option<bool>,
label: Option<&str>,
) -> Result<GraphCycleCheck> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
put(&mut body, "directed", directed);
put(&mut body, "label", label);
let response = self.send(op(json!({"DetectCycle": Value::Object(body)})))?;
response.decode("DetectCycle")
}
pub fn graph_stats(&mut self, graph: &str) -> Result<GraphStats> {
let response = self.send(op(json!({"GraphStats": {"graph": graph}})))?;
response.decode("GraphStats")
}
pub fn graph_subgraph(
&mut self,
graph: &str,
seeds: &[&str],
options: &TraverseOptions,
) -> Result<GraphSubgraph> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("seeds".into(), json!(seeds));
put_walk(&mut body, options);
let response = self.send(op(json!({"Subgraph": Value::Object(body)})))?;
response.decode("Subgraph")
}
pub fn graph_schema(&mut self, graph: &str) -> Result<GraphSchema> {
let response = self.send(op(json!({"GraphSchema": {"graph": graph}})))?;
response.decode("GraphSchema")
}
pub fn graph_k_shortest_paths(
&mut self,
graph: &str,
from: &str,
to: &str,
options: &KPathOptions,
) -> Result<GraphPaths> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("from".into(), json!(from));
body.insert("to".into(), json!(to));
put(&mut body, "k", options.k.filter(|k| *k > 0));
put(&mut body, "direction", direction_value(options.direction));
put(&mut body, "label", options.label.clone());
put(
&mut body,
"weight_property",
options.weight_property.clone(),
);
let response = self.send(op(json!({"KShortestPaths": Value::Object(body)})))?;
response.decode("KShortestPaths")
}
pub fn graph_all_paths(
&mut self,
graph: &str,
from: &str,
to: &str,
options: &TraverseOptions,
) -> Result<GraphPaths> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("from".into(), json!(from));
body.insert("to".into(), json!(to));
put_walk(&mut body, options);
let response = self.send(op(json!({"AllPaths": Value::Object(body)})))?;
response.decode("AllPaths")
}
pub fn graph_centrality(
&mut self,
graph: &str,
measure: CentralityMeasure,
options: &CentralityOptions,
) -> Result<GraphCentrality> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("measure".into(), json!(measure));
put(&mut body, "directed", options.directed);
put(&mut body, "label", options.label.clone());
put(&mut body, "limit", options.limit);
put(&mut body, "iterations", options.iterations);
put(&mut body, "tolerance", options.tolerance);
let response = self.send(op(json!({"Centrality": Value::Object(body)})))?;
response.decode("Centrality")
}
pub fn graph_communities(
&mut self,
graph: &str,
options: &CommunityOptions,
) -> Result<GraphCommunities> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
put(&mut body, "label", options.label.clone());
put(&mut body, "iterations", options.iterations);
put(&mut body, "limit", options.limit);
let response = self.send(op(json!({"Communities": Value::Object(body)})))?;
response.decode("Communities")
}
pub fn graph_triangle_count(
&mut self,
graph: &str,
label: Option<&str>,
limit: Option<usize>,
) -> Result<GraphTriangles> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
put(&mut body, "label", label);
put(&mut body, "limit", limit);
let response = self.send(op(json!({"TriangleCount": Value::Object(body)})))?;
response.decode("TriangleCount")
}
pub fn graph_node_similarity(
&mut self,
graph: &str,
node: &str,
options: &SimilarityOptions,
) -> Result<Vec<GraphScore>> {
#[derive(Deserialize)]
struct Similar {
#[serde(default)]
results: Vec<GraphScore>,
}
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("node".into(), json!(node));
put(&mut body, "direction", direction_value(options.direction));
put(&mut body, "label", options.label.clone());
put(&mut body, "metric", options.metric.map(|m| json!(m)));
put(&mut body, "limit", options.limit);
let response = self.send(op(json!({"NodeSimilarity": Value::Object(body)})))?;
let out: Similar = response.decode("NodeSimilarity")?;
Ok(out.results)
}
pub fn graph_check_consistency(&mut self, graph: &str) -> Result<GraphConsistency> {
let response = self.send(op(json!({"CheckConsistency": {"graph": graph}})))?;
response.decode("CheckConsistency")
}
pub fn graph_create_node_index(
&mut self,
graph: &str,
name: &str,
label: Option<&str>,
properties: &[&str],
) -> Result<()> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
body.insert("name".into(), json!(name));
put(&mut body, "label", label);
body.insert("properties".into(), json!(properties));
self.send(op(json!({"CreateNodeIndex": Value::Object(body)})))
.map(|_| ())
}
pub fn graph_create_constraint(
&mut self,
graph: &str,
name: &str,
label: &str,
property: &str,
kind: ConstraintKind,
target: ConstraintTarget,
) -> Result<()> {
self.send(op(json!({"CreateConstraint": {
"graph": graph,
"name": name,
"label": label,
"property": property,
"kind": kind,
"target": target,
}})))
.map(|_| ())
}
pub fn graph_drop_schema_object(&mut self, graph: &str, name: &str) -> Result<()> {
self.send(op(
json!({"DropSchemaObject": {"graph": graph, "name": name}}),
))
.map(|_| ())
}
pub fn graph_list_schema_objects(&mut self, graph: &str) -> Result<GraphSchemaObjects> {
let response = self.send(op(json!({"ListSchemaObjects": {"graph": graph}})))?;
response.decode("ListSchemaObjects")
}
pub fn graph_find_nodes(
&mut self,
graph: &str,
label: Option<&str>,
properties: Option<&Map<String, Value>>,
limit: Option<usize>,
) -> Result<GraphFoundNodes> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
put(&mut body, "label", label);
put(&mut body, "properties", properties.cloned());
put(&mut body, "limit", limit);
let response = self.send(op(json!({"FindNodes": Value::Object(body)})))?;
response.decode("FindNodes")
}
pub fn graph_import_csv(
&mut self,
graph: &str,
nodes: Option<&str>,
edges: Option<&str>,
) -> Result<GraphCounts> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
put(&mut body, "nodes", nodes);
put(&mut body, "edges", edges);
let response = self.send(op(json!({"ImportCsv": Value::Object(body)})))?;
response.decode("ImportCsv")
}
pub fn graph_page_rank(
&mut self,
graph: &str,
options: &PageRankOptions,
) -> Result<GraphPageRank> {
let mut body = Map::new();
body.insert("graph".into(), json!(graph));
put(&mut body, "damping", options.damping);
put(&mut body, "iterations", options.iterations);
put(&mut body, "tolerance", options.tolerance);
put(&mut body, "label", options.label.clone());
put(&mut body, "limit", options.limit);
let response = self.send(op(json!({"PageRank": Value::Object(body)})))?;
response.decode("PageRank")
}
pub fn graph_execute(&mut self, graph: &str, cypher: &str) -> Result<GraphWriteSummary> {
let response = self.send(op(json!({"Execute": {"graph": graph, "cypher": cypher}})))?;
response.decode("Execute")
}
pub fn graph_begin_transaction(&mut self, graph: &str) -> Result<String> {
#[derive(Deserialize)]
struct Begun {
#[serde(default)]
transaction: String,
}
let response = self.send_in(op(json!({"BeginTransaction": {"graph": graph}})), None)?;
let out: Begun = response.decode("BeginTransaction")?;
if out.transaction.is_empty() {
return Err(Error::protocol(
"BeginTransaction answered with no transaction id",
));
}
Ok(out.transaction)
}
pub fn graph_commit_transaction(
&mut self,
graph: &str,
txn: &str,
) -> Result<GraphWriteSummary> {
let outcome = self.send_in(
op(json!({"CommitTransaction": {"graph": graph}})),
Some(txn),
);
self.forget_txn(txn);
outcome?.decode("CommitTransaction")
}
pub fn graph_rollback_transaction(&mut self, graph: &str, txn: &str) -> Result<i64> {
#[derive(Deserialize)]
struct RolledBack {
#[serde(default)]
rolled_back: i64,
}
let outcome = self.send_in(
op(json!({"RollbackTransaction": {"graph": graph}})),
Some(txn),
);
self.forget_txn(txn);
let out: RolledBack = outcome?.decode("RollbackTransaction")?;
Ok(out.rolled_back)
}
}
fn put_walk(body: &mut Map<String, Value>, options: &TraverseOptions) {
put(body, "direction", direction_value(options.direction));
put(body, "label", options.label.clone());
put(body, "max_depth", options.max_depth.filter(|d| *d > 0));
put(body, "limit", options.limit.filter(|l| *l > 0));
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn directions_use_the_servers_spelling() {
assert_eq!(
serde_json::to_value(GraphDirection::Outgoing).unwrap(),
"outgoing"
);
assert_eq!(
serde_json::to_value(GraphDirection::Incoming).unwrap(),
"incoming"
);
assert_eq!(serde_json::to_value(GraphDirection::Both).unwrap(), "both");
assert_eq!(GraphDirection::default(), GraphDirection::Outgoing);
}
#[test]
fn an_unset_option_is_left_out_rather_than_sent_empty() {
let mut body = Map::new();
put(&mut body, "label", None::<String>);
put(&mut body, "limit", None::<usize>);
assert!(body.is_empty());
put(&mut body, "label", Some("FOLLOWS".to_string()));
put(&mut body, "limit", Some(10usize));
assert_eq!(body["label"], "FOLLOWS");
assert_eq!(body["limit"], 10);
}
#[test]
fn a_zero_bound_is_the_servers_default_not_a_bound_of_zero() {
let options = TraverseOptions {
max_depth: Some(0),
..Default::default()
};
let mut body = Map::new();
put(&mut body, "max_depth", options.max_depth.filter(|d| *d > 0));
assert!(body.is_empty());
}
#[test]
fn a_path_that_was_not_found_still_decodes() {
let path: GraphPath =
serde_json::from_value(json!({"found": false, "message": "no path within 10 hops"}))
.unwrap();
assert!(!path.found);
assert_eq!(path.hops, 0);
assert!(path.node_path.is_empty());
}
}