Repository: neo4j-labs/graph (language: rust)

Issue:
Hide `GDL` support behind a feature flag
as suggested by @knutwalker 


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index e39a9121..6bd63089 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -23,6 +23,7 @@ jobs:
       - uses: actions-rs/cargo@v1
         with:
           command: check
+          args: --features gdl
 
   test:
     name: Test Suite
@@ -37,10 +38,11 @@ jobs:
       - uses: actions-rs/cargo@v1
         with:
           command: test
+          args: --features gdl
       - uses: actions-rs/cargo@v1
         with:
           command: test
-          args: --examples
+          args: --examples --features gdl
 
   fmt:
     name: Rustfmt
@@ -72,4 +74,4 @@ jobs:
       - uses: actions-rs/cargo@v1
         with:
           command: clippy
-          args: -- -D warnings
+          args: --features gdl -- -D warnings
diff --git a/Cargo.toml b/Cargo.toml
index 2e673240..bd3657f9 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -17,7 +17,7 @@ byte-slice-cast = "1.1.0"
 delegate = "0.6.1"
 fast-float = "0.2.0"
 fxhash = "0.2.1"
-gdl = "0.2.4"
+gdl = { version = "0.2.4", optional = true }
 linereader = "0.4.0"
 log = "0.4.14"
 memmap2 = "0.5.0"
diff --git a/src/builder.rs b/src/builder.rs
index 71eed641..5b893d8d 100644
--- a/src/builder.rs
+++ b/src/builder.rs
@@ -42,6 +42,7 @@ where
     _node: PhantomData<NI>,
 }
 
+#[doc(cfg(feature = "gdl"))]
 pub struct FromGdlString<NI>
 where
     NI: Idx,
@@ -51,6 +52,7 @@ where
     _node: PhantomData<NI>,
 }
 
+#[doc(cfg(feature = "gdl"))]
 pub struct FromGdlGraph<'a, NI>
 where
     NI: Idx,
@@ -268,6 +270,7 @@ impl GraphBuilder<Uninitialized> {
     /// assert_eq!(g.edge_count(), 2);
     /// assert_eq!(g.neighbors_with_values(0), &[Target::new(1, 0.42), Target::new(2, 13.37)]);
     /// ```
+    #[doc(cfg(feature = "gdl"))]
     pub fn gdl_str<NI, S>(self, gdl: S) -> GraphBuilder<FromGdlString<NI>>
     where
         NI: Idx,
@@ -295,7 +298,7 @@ impl GraphBuilder<Uninitialized> {
     /// ```
     /// use graph::prelude::*;
     ///
-    /// let gdl_graph = "(a)-->(),(a)-->()".parse::<gdl::Graph>().unwrap();
+    /// let gdl_graph = "(a)-->(),(a)-->()".parse::<::gdl::Graph>().unwrap();
     ///
     /// let g: DirectedCsrGraph<usize> = GraphBuilder::new()
     ///     .gdl_graph::<usize>(&gdl_graph)
@@ -309,7 +312,8 @@ impl GraphBuilder<Uninitialized> {
     ///
     /// assert_eq!(g.out_neighbors(id_a).len(), 2);
     /// ```
-    pub fn gdl_graph<NI>(self, gdl_graph: &gdl::Graph) -> GraphBuilder<FromGdlGraph<NI>>
+    #[doc(cfg(feature = "gdl"))]
+    pub fn gdl_graph<NI>(self, gdl_graph: &::gdl::Graph) -> GraphBuilder<FromGdlGraph<NI>>
     where
         NI: Idx,
     {
@@ -474,6 +478,7 @@ impl<NI: Idx, NV, EV> GraphBuilder<FromEdgeListAndNodeValues<NI, NV, EV>> {
     }
 }
 
+#[doc(cfg(feature = "gdl"))]
 impl<NI> GraphBuilder<FromGdlString<NI>>
 where
     NI: Idx,
@@ -489,6 +494,7 @@ where
     }
 }
 
+#[doc(cfg(feature = "gdl"))]
 impl<'a, NI> GraphBuilder<FromGdlGraph<'a, NI>>
 where
     NI: Idx,
diff --git a/src/graph/csr.rs b/src/graph/csr.rs
index e1fd472f..64335c88 100644
--- a/src/graph/csr.rs
+++ b/src/graph/csr.rs
@@ -17,7 +17,7 @@ use rayon::prelude::*;
 use crate::{
     graph_ops::{DeserializeGraphOp, SerializeGraphOp},
     index::{AtomicIdx, Idx},
-    input::{edgelist::EdgeList, Direction, DotGraph, MyCypherValue},
+    input::{edgelist::EdgeList, Direction, DotGraph},
     DirectedDegrees, DirectedNeighbors, DirectedNeighborsWithValues, Error, Graph,
     NodeValues as NodeValuesTrait, SharedMut, UndirectedDegrees, UndirectedNeighbors,
     UndirectedNeighborsWithValues,
@@ -550,16 +550,6 @@ where
     }
 }
 
-impl<'a, NI, EV> From<(&'a gdl::Graph, CsrLayout)> for DirectedCsrGraph<NI, (), EV>
-where
-    NI: Idx,
-    EV: From<MyCypherValue<'a>> + Default + Copy + Send + Sync,
-{
-    fn from((gdl_graph, csr_layout): (&'a gdl::Graph, CsrLayout)) -> Self {
-        DirectedCsrGraph::from((EdgeList::from(gdl_graph), csr_layout))
-    }
-}
-
 impl<W, NI, NV, EV> SerializeGraphOp<W> for DirectedCsrGraph<NI, NV, EV>
 where
     W: Write,
@@ -582,16 +572,6 @@ where
     }
 }
 
-impl<NI, EV> From<(gdl::Graph, CsrLayout)> for DirectedCsrGraph<NI, (), EV>
-where
-    NI: Idx,
-    for<'a> EV: From<MyCypherValue<'a>> + Default + Copy + Send + Sync,
-{
-    fn from((gdl_graph, csr_layout): (gdl::Graph, CsrLayout)) -> Self {
-        DirectedCsrGraph::from((EdgeList::from(&gdl_graph), csr_layout))
-    }
-}
-
 impl<R, NI, NV, EV> DeserializeGraphOp<R, Self> for DirectedCsrGraph<NI, NV, EV>
 where
     R: Read,
@@ -774,26 +754,6 @@ where
     }
 }
 
-impl<'a, NI, EV> From<(&'a gdl::Graph, CsrLayout)> for UndirectedCsrGraph<NI, (), EV>
-where
-    NI: Idx,
-    EV: From<MyCypherValue<'a>> + Default + Copy + Send + Sync,
-{
-    fn from((gdl_graph, csr_layout): (&'a gdl::Graph, CsrLayout)) -> Self {
-        UndirectedCsrGraph::from((EdgeList::from(gdl_graph), csr_layout))
-    }
-}
-
-impl<NI, EV> From<(gdl::Graph, CsrLayout)> for UndirectedCsrGraph<NI, (), EV>
-where
-    NI: Idx,
-    for<'a> EV: From<MyCypherValue<'a>> + Default + Copy + Send + Sync,
-{
-    fn from((gdl_graph, csr_layout): (gdl::Graph, CsrLayout)) -> Self {
-        UndirectedCsrGraph::from((EdgeList::from(&gdl_graph), csr_layout))
-    }
-}
-
 impl<W, NI, NV, EV> SerializeGraphOp<W> for UndirectedCsrGraph<NI, NV, EV>
 where
     W: Write,
diff --git a/src/input/dotgraph.rs b/src/input/dotgraph.rs
index 2e1f8b24..6b4d44d6 100644
--- a/src/input/dotgraph.rs
+++ b/src/input/dotgraph.rs
@@ -1,6 +1,6 @@
 use std::{
-    convert::TryFrom, fmt::Write, fs::File, hash::Hash, intrinsics::transmute, io::Read,
-    marker::PhantomData, path::Path, sync::atomic::Ordering::Acquire,
+    convert::TryFrom, fs::File, hash::Hash, intrinsics::transmute, io::Read, marker::PhantomData,
+    path::Path, sync::atomic::Ordering::Acquire,
 };
 
 use fxhash::FxHashMap;
@@ -262,73 +262,6 @@ where
     }
 }
 
-impl<NI, Label> From<&gdl::Graph> for DotGraph<NI, Label>
-where
-    NI: Idx,
-    Label: Idx + Hash,
-{
-    /// Converts the given GDL graph into a .graph input string.
-    ///
-    /// Node labels need to be numeric, however GDL does not support numeric
-    /// labels. In order to circumvent this, node labels need to be prefixed
-    /// with a single character, e.g. `(n:L0)` to declare label `0`.
-    fn from(gdl_graph: &gdl::Graph) -> Self {
-        fn degree(gdl_graph: &gdl::Graph, node: &gdl::graph::Node) -> usize {
-            let mut degree = 0;
-
-            for rel in gdl_graph.relationships() {
-                if rel.source() == node.variable() {
-                    degree += 1;
-                }
-                if rel.target() == node.variable() {
-                    degree += 1;
-                }
-            }
-            degree
-        }
-
-        let header = format!(
-            "t {} {}",
-            gdl_graph.node_count(),
-            gdl_graph.relationship_count()
-        );
-
-        let mut nodes_string = String::from("");
-
-        let mut sorted_nodes = gdl_graph.nodes().collect::<Vec<_>>();
-        sorted_nodes.sort_by_key(|node| node.id());
-
-        for node in sorted_nodes {
-            let id = node.id();
-            let label = node.labels().next().expect("Single label expected");
-            let degree = degree(gdl_graph, node);
-            let _ = writeln!(nodes_string, "v {} {} {}", id, &label[1..], degree);
-        }
-
-        let mut rels_string = String::from("");
-
-        let mut sorted_rels = gdl_graph.relationships().collect::<Vec<_>>();
-        sorted_rels.sort_by_key(|rel| (rel.source(), rel.target()));
-
-        for rel in sorted_rels {
-            let source_id = gdl_graph
-                .get_node(rel.source())
-                .expect("Source expected")
-                .id();
-            let target_id = gdl_graph
-                .get_node(rel.target())
-                .expect("Target expected")
-                .id();
-            let _ = writeln!(rels_string, "e {} {}", source_id, target_id);
-        }
-
-        let input = format!("{}\n{}{}", header, nodes_string, rels_string);
-        let reader = LineReader::new(input.as_bytes());
-
-        DotGraph::<NI, Label>::try_from(reader).expect("GDL to .graph conversion failed")
-    }
-}
-
 #[cfg(test)]
 mod tests {
     use std::path::PathBuf;
diff --git a/src/input/edgelist.rs b/src/input/edgelist.rs
index dd25d258..4d5103bb 100644
--- a/src/input/edgelist.rs
+++ b/src/input/edgelist.rs
@@ -17,7 +17,7 @@ use std::{
 
 use crate::{input::Direction, Error};
 
-use super::{InputCapabilities, InputPath, MyCypherValue, ParseValue};
+use super::{InputCapabilities, InputPath, ParseValue};
 
 /// Reads a graph from a file that contains an edge per line.
 ///
@@ -129,33 +129,6 @@ where
     }
 }
 
-impl<'gdl, NI, EV> From<&'gdl gdl::Graph> for EdgeList<NI, EV>
-where
-    NI: Idx,
-    EV: From<MyCypherValue<'gdl>> + Default + Send + Sync,
-{
-    fn from(gdl_graph: &'gdl gdl::Graph) -> Self {
-        let edges = gdl_graph
-            .relationships()
-            .into_iter()
-            .map(|r| {
-                let source = gdl_graph.get_node(r.source()).unwrap().id();
-                let target = gdl_graph.get_node(r.target()).unwrap().id();
-
-                let value = if let Some(k) = r.property_keys().next() {
-                    EV::from(MyCypherValue(r.property_value(k).unwrap()))
-                } else {
-                    EV::default()
-                };
-
-                (NI::new(source), NI::new(target), value)
-            })
-            .collect::<Vec<_>>();
-
-        EdgeList::new(edges)
-    }
-}
-
 impl<NI, P, EV> TryFrom<InputPath<P>> for EdgeList<NI, EV>
 where
     P: AsRef<Path>,
diff --git a/src/input/gdl.rs b/src/input/gdl.rs
new file mode 100644
index 00000000..16bfbbad
--- /dev/null
+++ b/src/input/gdl.rs
@@ -0,0 +1,168 @@
+use crate::graph::csr::{CsrLayout, DirectedCsrGraph, UndirectedCsrGraph};
+use crate::index::Idx;
+use crate::input::{DotGraph, EdgeList};
+
+use gdl::CypherValue;
+use linereader::LineReader;
+use std::fmt::Write;
+use std::hash::Hash;
+
+/// A wrapper around [`gdl::CypherValue`] to allow custom From implementations.
+pub struct MyCypherValue<'a>(&'a CypherValue);
+
+impl<'a> From<MyCypherValue<'a>> for () {
+    fn from(_: MyCypherValue) -> Self {}
+}
+
+macro_rules! impl_from_cypher_value {
+    ($enum:path, $ty:ty) => {
+        impl<'a> ::std::convert::From<$crate::input::gdl::MyCypherValue<'a>> for $ty {
+            fn from(cv: $crate::input::gdl::MyCypherValue) -> Self {
+                if let $enum(f) = cv.0 {
+                    *f as $ty
+                } else {
+                    panic!("expected {} value", stringify!($ty))
+                }
+            }
+        }
+    };
+}
+
+impl_from_cypher_value!(CypherValue::Float, f32);
+impl_from_cypher_value!(CypherValue::Float, f64);
+impl_from_cypher_value!(CypherValue::Integer, i32);
+impl_from_cypher_value!(CypherValue::Integer, i64);
+
+impl<'gdl, NI, EV> From<&'gdl gdl::Graph> for EdgeList<NI, EV>
+where
+    NI: Idx,
+    EV: From<MyCypherValue<'gdl>> + Default + Send + Sync,
+{
+    fn from(gdl_graph: &'gdl gdl::Graph) -> Self {
+        let edges = gdl_graph
+            .relationships()
+            .into_iter()
+            .map(|r| {
+                let source = gdl_graph.get_node(r.source()).unwrap().id();
+                let target = gdl_graph.get_node(r.target()).unwrap().id();
+
+                let value = if let Some(k) = r.property_keys().next() {
+                    EV::from(MyCypherValue(r.property_value(k).unwrap()))
+                } else {
+                    EV::default()
+                };
+
+                (NI::new(source), NI::new(target), value)
+            })
+            .collect::<Vec<_>>();
+
+        EdgeList::new(edges)
+    }
+}
+
+impl<NI, Label> From<&gdl::Graph> for DotGraph<NI, Label>
+where
+    NI: Idx,
+    Label: Idx + Hash,
+{
+    /// Converts the given GDL graph into a .graph input string.
+    ///
+    /// Node labels need to be numeric, however GDL does not support numeric
+    /// labels. In order to circumvent this, node labels need to be prefixed
+    /// with a single character, e.g. `(n:L0)` to declare label `0`.
+    fn from(gdl_graph: &gdl::Graph) -> Self {
+        fn degree(gdl_graph: &gdl::Graph, node: &gdl::graph::Node) -> usize {
+            let mut degree = 0;
+
+            for rel in gdl_graph.relationships() {
+                if rel.source() == node.variable() {
+                    degree += 1;
+                }
+                if rel.target() == node.variable() {
+                    degree += 1;
+                }
+            }
+            degree
+        }
+
+        let header = format!(
+            "t {} {}",
+            gdl_graph.node_count(),
+            gdl_graph.relationship_count()
+        );
+
+        let mut nodes_string = String::from("");
+
+        let mut sorted_nodes = gdl_graph.nodes().collect::<Vec<_>>();
+        sorted_nodes.sort_by_key(|node| node.id());
+
+        for node in sorted_nodes {
+            let id = node.id();
+            let label = node.labels().next().expect("Single label expected");
+            let degree = degree(gdl_graph, node);
+            let _ = writeln!(nodes_string, "v {} {} {}", id, &label[1..], degree);
+        }
+
+        let mut rels_string = String::from("");
+
+        let mut sorted_rels = gdl_graph.relationships().collect::<Vec<_>>();
+        sorted_rels.sort_by_key(|rel| (rel.source(), rel.target()));
+
+        for rel in sorted_rels {
+            let source_id = gdl_graph
+                .get_node(rel.source())
+                .expect("Source expected")
+                .id();
+            let target_id = gdl_graph
+                .get_node(rel.target())
+                .expect("Target expected")
+                .id();
+            let _ = writeln!(rels_string, "e {} {}", source_id, target_id);
+        }
+
+        let input = format!("{}\n{}{}", header, nodes_string, rels_string);
+        let reader = LineReader::new(input.as_bytes());
+
+        DotGraph::<NI, Label>::try_from(reader).expect("GDL to .graph conversion failed")
+    }
+}
+
+impl<'a, NI, EV> From<(&'a gdl::Graph, CsrLayout)> for DirectedCsrGraph<NI, (), EV>
+where
+    NI: Idx,
+    EV: From<MyCypherValue<'a>> + Default + Copy + Send + Sync,
+{
+    fn from((gdl_graph, csr_layout): (&'a gdl::Graph, CsrLayout)) -> Self {
+        DirectedCsrGraph::from((EdgeList::from(gdl_graph), csr_layout))
+    }
+}
+
+impl<NI, EV> From<(gdl::Graph, CsrLayout)> for DirectedCsrGraph<NI, (), EV>
+where
+    NI: Idx,
+    for<'a> EV: From<MyCypherValue<'a>> + Default + Copy + Send + Sync,
+{
+    fn from((gdl_graph, csr_layout): (gdl::Graph, CsrLayout)) -> Self {
+        DirectedCsrGraph::from((EdgeList::from(&gdl_graph), csr_layout))
+    }
+}
+
+impl<'a, NI, EV> From<(&'a gdl::Graph, CsrLayout)> for UndirectedCsrGraph<NI, (), EV>
+where
+    NI: Idx,
+    EV: From<MyCypherValue<'a>> + Default + Copy + Send + Sync,
+{
+    fn from((gdl_graph, csr_layout): (&'a gdl::Graph, CsrLayout)) -> Self {
+        UndirectedCsrGraph::from((EdgeList::from(gdl_graph), csr_layout))
+    }
+}
+
+impl<NI, EV> From<(gdl::Graph, CsrLayout)> for UndirectedCsrGraph<NI, (), EV>
+where
+    NI: Idx,
+    for<'a> EV: From<MyCypherValue<'a>> + Default + Copy + Send + Sync,
+{
+    fn from((gdl_graph, csr_layout): (gdl::Graph, CsrLayout)) -> Self {
+        UndirectedCsrGraph::from((EdgeList::from(&gdl_graph), csr_layout))
+    }
+}
diff --git a/src/input/mod.rs b/src/input/mod.rs
index af279046..6a2c1cac 100644
--- a/src/input/mod.rs
+++ b/src/input/mod.rs
@@ -1,6 +1,8 @@
 pub mod binary;
 pub mod dotgraph;
 pub mod edgelist;
+#[doc(cfg(feature = "gdl"))]
+pub mod gdl;
 
 pub use binary::BinaryInput;
 pub use dotgraph::DotGraph;
@@ -9,7 +11,6 @@ pub use edgelist::EdgeList;
 pub use edgelist::EdgeListInput;
 
 use crate::index::Idx;
-use gdl::CypherValue;
 
 pub struct InputPath<P>(pub(crate) P);
 
@@ -95,29 +96,3 @@ impl_parse_value!(parse_float, f32, f64);
 fn parse_float<T: fast_float::FastFloat>(bytes: &[u8]) -> (T, usize) {
     fast_float::parse_partial(bytes).unwrap()
 }
-
-/// A wrapper around [`gdl::CypherValue`] to allow custom From implementations.
-pub struct MyCypherValue<'a>(&'a CypherValue);
-
-impl<'a> From<MyCypherValue<'a>> for () {
-    fn from(_: MyCypherValue) -> Self {}
-}
-
-macro_rules! impl_from_cypher_value {
-    ($enum:path, $ty:ty) => {
-        impl<'a> ::std::convert::From<$crate::input::MyCypherValue<'a>> for $ty {
-            fn from(cv: $crate::input::MyCypherValue) -> Self {
-                if let $enum(f) = cv.0 {
-                    *f as $ty
-                } else {
-                    panic!("expected {} value", stringify!($ty))
-                }
-            }
-        }
-    };
-}
-
-impl_from_cypher_value!(CypherValue::Float, f32);
-impl_from_cypher_value!(CypherValue::Float, f64);
-impl_from_cypher_value!(CypherValue::Integer, i32);
-impl_from_cypher_value!(CypherValue::Integer, i64);
diff --git a/src/lib.rs b/src/lib.rs
index 38a9662b..38ca7e96 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -4,6 +4,7 @@
 #![feature(maybe_uninit_slice)]
 #![feature(step_trait)]
 #![feature(new_uninit)]
+#![feature(doc_cfg)]
 #![allow(dead_code)]
 
 //! A library that can be used as a building block for high-performant graph
@@ -187,6 +188,7 @@ pub enum Error {
         #[from]
         source: std::io::Error,
     },
+    #[doc(cfg(feature = "gdl"))]
     #[error("error while parsing GDL input")]
     GdlError {
         #[from]


Test patch:
diff --git a/tests/builder.rs b/tests/builder.rs
index 9b2b0e86..5863a43a 100644
--- a/tests/builder.rs
+++ b/tests/builder.rs
@@ -128,6 +128,7 @@ fn directed_usize_graph_from_edge_list_with_values() {
     );
 }
 
+#[cfg(feature = "gdl")]
 #[test]
 fn directed_usize_graph_from_gdl() {
     assert_directed_graph::<usize, ()>(
@@ -140,6 +141,7 @@ fn directed_usize_graph_from_gdl() {
     )
 }
 
+#[cfg(feature = "gdl")]
 #[test]
 fn directed_usize_graph_from_gdl_with_f32_edge_values() {
     let g: DirectedCsrGraph<usize, (), f32> = GraphBuilder::new()
@@ -159,6 +161,7 @@ fn directed_usize_graph_from_gdl_with_f32_edge_values() {
     assert_eq!(actual, &[Target::new(1, 0.1), Target::new(2, 0.2)]);
 }
 
+#[cfg(feature = "gdl")]
 #[test]
 fn directed_usize_graph_from_gdl_with_i64_edge_values() {
     let g: DirectedCsrGraph<usize, (), i64> = GraphBuilder::new()
@@ -178,6 +181,7 @@ fn directed_usize_graph_from_gdl_with_i64_edge_values() {
     assert_eq!(actual, &[Target::new(1, 42), Target::new(2, 43)]);
 }
 
+#[cfg(feature = "gdl")]
 #[test]
 fn undirected_usize_graph_from_gdl() {
     assert_undirected_graph::<usize, ()>(