1use std::cmp::Ordering;
2use std::collections::{BTreeMap, BTreeSet};
3use std::fmt;
4use std::ops::Deref;
5use std::slice;
6use std::sync::Arc;
7
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use thiserror::Error;
11
12use crate::ExternalId;
13
14pub type NodeId = ExternalId;
16pub type Attributes = BTreeMap<String, Value>;
18
19#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
24pub struct EdgeId {
25 stored_id: String,
26 reverse_generation: u64,
27}
28
29impl EdgeId {
30 pub fn original(id: impl Into<String>) -> Self {
32 Self {
33 stored_id: id.into(),
34 reverse_generation: 0,
35 }
36 }
37
38 pub fn synthesized_reverse(id: impl Into<String>, generation: u64) -> Option<Self> {
40 (generation > 0).then(|| Self {
41 stored_id: id.into(),
42 reverse_generation: generation,
43 })
44 }
45
46 pub fn reversed(&self) -> Option<Self> {
48 self.reverse_generation
49 .checked_add(1)
50 .and_then(|generation| Self::synthesized_reverse(self.stored_id.clone(), generation))
51 }
52
53 pub fn stored_id(&self) -> &str {
55 &self.stored_id
56 }
57
58 pub const fn reverse_generation(&self) -> u64 {
60 self.reverse_generation
61 }
62
63 fn is_valid(&self) -> bool {
64 !self.stored_id.is_empty()
65 }
66}
67
68impl From<String> for EdgeId {
69 fn from(id: String) -> Self {
70 Self::original(id)
71 }
72}
73
74impl From<&str> for EdgeId {
75 fn from(id: &str) -> Self {
76 Self::original(id)
77 }
78}
79
80impl fmt::Display for EdgeId {
81 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
82 if self.reverse_generation == 0 {
83 formatter.write_str(&self.stored_id)
84 } else {
85 write!(
86 formatter,
87 "reverse#{}({})",
88 self.reverse_generation, self.stored_id
89 )
90 }
91 }
92}
93
94#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
96#[serde(try_from = "f64", into = "f64")]
97pub struct PositiveFiniteF64(f64);
98
99impl PositiveFiniteF64 {
100 pub fn new(value: f64) -> Result<Self, GraphError> {
102 if value.is_finite() && value > 0.0 {
103 Ok(Self(value))
104 } else {
105 Err(GraphError::InvalidOption(
106 "value must be finite and positive".to_string(),
107 ))
108 }
109 }
110
111 pub const fn get(self) -> f64 {
113 self.0
114 }
115}
116
117impl TryFrom<f64> for PositiveFiniteF64 {
118 type Error = GraphError;
119
120 fn try_from(value: f64) -> Result<Self, Self::Error> {
121 Self::new(value)
122 }
123}
124
125impl From<PositiveFiniteF64> for f64 {
126 fn from(value: PositiveFiniteF64) -> Self {
127 value.get()
128 }
129}
130
131#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Serialize, Deserialize)]
133#[serde(try_from = "f64", into = "f64")]
134pub struct NonNegativeFiniteF64(f64);
135
136impl NonNegativeFiniteF64 {
137 pub fn new(value: f64) -> Result<Self, GraphError> {
139 if value.is_finite() && value >= 0.0 {
140 Ok(Self(value))
141 } else {
142 Err(GraphError::InvalidOption(
143 "value must be finite and non-negative".to_string(),
144 ))
145 }
146 }
147
148 pub const fn get(self) -> f64 {
150 self.0
151 }
152}
153
154impl TryFrom<f64> for NonNegativeFiniteF64 {
155 type Error = GraphError;
156
157 fn try_from(value: f64) -> Result<Self, Self::Error> {
158 Self::new(value)
159 }
160}
161
162impl From<NonNegativeFiniteF64> for f64 {
163 fn from(value: NonNegativeFiniteF64) -> Self {
164 value.get()
165 }
166}
167
168#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
170#[serde(rename_all = "snake_case")]
171pub enum GraphKind {
172 Graph,
174 DiGraph,
176 MultiGraph,
178 MultiDiGraph,
180}
181
182impl GraphKind {
183 pub const fn is_directed(self) -> bool {
185 matches!(self, Self::DiGraph | Self::MultiDiGraph)
186 }
187
188 pub const fn is_multigraph(self) -> bool {
190 matches!(self, Self::MultiGraph | Self::MultiDiGraph)
191 }
192}
193
194#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
196pub struct Node {
197 pub id: NodeId,
199 pub label: Option<String>,
201 pub attributes: Attributes,
203}
204
205impl Node {
206 pub fn new(id: impl Into<NodeId>) -> Self {
208 Self {
209 id: id.into(),
210 label: None,
211 attributes: Attributes::new(),
212 }
213 }
214
215 pub fn with_label(mut self, label: impl Into<String>) -> Self {
217 self.label = Some(label.into());
218 self
219 }
220
221 pub fn with_attributes(mut self, attributes: Attributes) -> Self {
223 self.attributes = attributes;
224 self
225 }
226}
227
228#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
230pub struct Edge {
231 pub id: EdgeId,
233 pub graphify_key: Option<ExternalId>,
235 pub source: NodeId,
237 pub target: NodeId,
239 pub label: Option<String>,
241 pub weight: Option<f64>,
243 pub attributes: Attributes,
245}
246
247impl Edge {
248 pub fn new(
250 id: impl Into<EdgeId>,
251 source: impl Into<NodeId>,
252 target: impl Into<NodeId>,
253 ) -> Self {
254 Self {
255 id: id.into(),
256 graphify_key: None,
257 source: source.into(),
258 target: target.into(),
259 label: None,
260 weight: None,
261 attributes: Attributes::new(),
262 }
263 }
264
265 pub fn with_graphify_key(mut self, key: impl Into<ExternalId>) -> Self {
267 self.graphify_key = Some(key.into());
268 self
269 }
270
271 pub fn with_label(mut self, label: impl Into<String>) -> Self {
273 self.label = Some(label.into());
274 self
275 }
276
277 pub fn with_weight(mut self, weight: f64) -> Self {
279 self.weight = Some(weight);
280 self
281 }
282
283 pub fn with_attributes(mut self, attributes: Attributes) -> Self {
285 self.attributes = attributes;
286 self
287 }
288}
289
290#[derive(Debug, Clone, PartialEq, Error)]
292pub enum GraphError {
293 #[error("edge ID must not be empty")]
295 EmptyEdgeId,
296 #[error("duplicate node ID: {0}")]
298 DuplicateNode(NodeId),
299 #[error("duplicate edge ID: {0}")]
301 DuplicateEdge(EdgeId),
302 #[error("edge {edge_id} references missing {endpoint} node {node_id}")]
304 MissingEndpoint {
305 edge_id: EdgeId,
307 endpoint: &'static str,
309 node_id: NodeId,
311 },
312 #[error("edge {edge_id} weight must be finite and non-negative, got {weight}")]
314 InvalidWeight {
315 edge_id: EdgeId,
317 weight: f64,
319 },
320 #[error("invalid external identity: {0}")]
322 InvalidExternalId(String),
323 #[error("unknown node ID: {0}")]
325 UnknownNode(NodeId),
326 #[error("unknown edge ID: {0}")]
328 UnknownEdge(EdgeId),
329 #[error("relabel target {target} is produced by both {first} and {second}")]
331 RelabelCollision {
332 target: NodeId,
334 first: NodeId,
336 second: NodeId,
338 },
339 #[error("{kind:?} does not permit parallel edges between {pair_source} and {pair_target}")]
341 ParallelEdge {
342 kind: GraphKind,
344 pair_source: NodeId,
346 pair_target: NodeId,
348 },
349 #[error("cannot compose graphs with different kinds")]
351 KindMismatch,
352 #[error("edge {edge_id} has conflicting endpoints across composed graphs")]
354 ConflictingEdge {
355 edge_id: EdgeId,
357 },
358 #[error("edge {stored_id} exhausted synthesized reverse generations")]
360 EdgeIdentityExhausted {
361 stored_id: String,
363 },
364 #[error("invalid algorithm option: {0}")]
366 InvalidOption(String),
367}
368
369#[derive(Debug, Clone, Copy, PartialEq, Eq)]
370pub(crate) struct ArcRef {
371 pub(crate) neighbor: usize,
372 pub(crate) edge: usize,
373}
374
375#[derive(Debug, Clone, PartialEq)]
376struct Csr {
377 offsets: Vec<usize>,
378 arcs: Vec<ArcRef>,
379}
380
381impl Csr {
382 fn from_rows(rows: Vec<Vec<ArcRef>>) -> Self {
383 let mut offsets = Vec::with_capacity(rows.len() + 1);
384 let mut arcs = Vec::with_capacity(rows.iter().map(Vec::len).sum());
385 offsets.push(0);
386 for row in rows {
387 arcs.extend(row);
388 offsets.push(arcs.len());
389 }
390 Self { offsets, arcs }
391 }
392
393 fn row(&self, node: usize) -> &[ArcRef] {
394 &self.arcs[self.offsets[node]..self.offsets[node + 1]]
395 }
396}
397
398#[derive(Debug, Clone, PartialEq)]
400pub struct Graph {
401 inner: Arc<GraphInner>,
402}
403
404#[doc(hidden)]
407#[derive(Debug, PartialEq)]
408pub struct GraphInner {
409 kind: GraphKind,
410 graph_attributes: Attributes,
411 nodes: Vec<Node>,
412 edges: Vec<Edge>,
413 node_indexes: BTreeMap<NodeId, usize>,
414 edge_indexes: BTreeMap<EdgeId, usize>,
415 outgoing: Csr,
416 incoming: Csr,
417}
418
419impl Deref for Graph {
420 type Target = GraphInner;
421
422 fn deref(&self) -> &Self::Target {
423 &self.inner
424 }
425}
426
427impl Graph {
428 pub fn new(
430 kind: GraphKind,
431 nodes: impl IntoIterator<Item = Node>,
432 edges: impl IntoIterator<Item = Edge>,
433 ) -> Result<Self, GraphError> {
434 Self::with_attributes(kind, Attributes::new(), nodes, edges)
435 }
436
437 pub fn with_attributes(
439 kind: GraphKind,
440 graph_attributes: Attributes,
441 nodes: impl IntoIterator<Item = Node>,
442 edges: impl IntoIterator<Item = Edge>,
443 ) -> Result<Self, GraphError> {
444 let mut node_map = BTreeMap::new();
445 for node in nodes {
446 node.id.validate()?;
447 let node_id = node.id.clone();
448 if node_map.insert(node_id.clone(), node).is_some() {
449 return Err(GraphError::DuplicateNode(node_id));
450 }
451 }
452 let nodes = node_map.into_values().collect::<Vec<_>>();
453 let node_indexes = nodes
454 .iter()
455 .enumerate()
456 .map(|(index, node)| (node.id.clone(), index))
457 .collect::<BTreeMap<_, _>>();
458
459 let mut edge_map = BTreeMap::new();
460 for edge in edges {
461 if !edge.id.is_valid() {
462 return Err(GraphError::EmptyEdgeId);
463 }
464 if !node_indexes.contains_key(&edge.source) {
465 return Err(GraphError::MissingEndpoint {
466 edge_id: edge.id,
467 endpoint: "source",
468 node_id: edge.source,
469 });
470 }
471 if !node_indexes.contains_key(&edge.target) {
472 return Err(GraphError::MissingEndpoint {
473 edge_id: edge.id,
474 endpoint: "target",
475 node_id: edge.target,
476 });
477 }
478 if let Some(weight) = edge.weight
479 && (!weight.is_finite() || weight < 0.0)
480 {
481 return Err(GraphError::InvalidWeight {
482 edge_id: edge.id,
483 weight,
484 });
485 }
486 let edge_id = edge.id.clone();
487 if edge_map.insert(edge_id.clone(), edge).is_some() {
488 return Err(GraphError::DuplicateEdge(edge_id));
489 }
490 }
491 let edges = edge_map.into_values().collect::<Vec<_>>();
492 if !kind.is_multigraph() {
493 let mut endpoint_pairs = BTreeSet::new();
494 for edge in &edges {
495 let pair = if kind.is_directed() || edge.source <= edge.target {
496 (edge.source.clone(), edge.target.clone())
497 } else {
498 (edge.target.clone(), edge.source.clone())
499 };
500 if !endpoint_pairs.insert(pair.clone()) {
501 return Err(GraphError::ParallelEdge {
502 kind,
503 pair_source: pair.0,
504 pair_target: pair.1,
505 });
506 }
507 }
508 }
509 let edge_indexes = edges
510 .iter()
511 .enumerate()
512 .map(|(index, edge)| (edge.id.clone(), index))
513 .collect::<BTreeMap<_, _>>();
514
515 let mut outgoing_rows = vec![Vec::new(); nodes.len()];
516 let mut incoming_rows = vec![Vec::new(); nodes.len()];
517 for (edge_index, edge) in edges.iter().enumerate() {
518 let source = node_indexes[&edge.source];
519 let target = node_indexes[&edge.target];
520 outgoing_rows[source].push(ArcRef {
521 neighbor: target,
522 edge: edge_index,
523 });
524 incoming_rows[target].push(ArcRef {
525 neighbor: source,
526 edge: edge_index,
527 });
528 }
529 for row in outgoing_rows.iter_mut().chain(incoming_rows.iter_mut()) {
530 row.sort_by(|left, right| compare_arcs(&nodes, &edges, left, right));
531 }
532
533 Ok(Self {
534 inner: Arc::new(GraphInner {
535 kind,
536 graph_attributes,
537 nodes,
538 edges,
539 node_indexes,
540 edge_indexes,
541 outgoing: Csr::from_rows(outgoing_rows),
542 incoming: Csr::from_rows(incoming_rows),
543 }),
544 })
545 }
546
547 pub fn kind(&self) -> GraphKind {
549 self.kind
550 }
551
552 pub fn is_directed(&self) -> bool {
554 self.kind.is_directed()
555 }
556
557 pub fn is_multigraph(&self) -> bool {
559 self.kind.is_multigraph()
560 }
561
562 pub fn attributes(&self) -> &Attributes {
564 &self.graph_attributes
565 }
566
567 pub fn node_count(&self) -> usize {
569 self.nodes.len()
570 }
571
572 pub fn edge_count(&self) -> usize {
575 self.edges.len()
576 }
577
578 pub fn nodes(&self) -> &[Node] {
580 &self.nodes
581 }
582
583 pub fn edges(&self) -> &[Edge] {
585 &self.edges
586 }
587
588 pub fn node(&self, id: impl Into<NodeId>) -> Option<&Node> {
590 let id = id.into();
591 self.node_indexes.get(&id).map(|index| &self.nodes[*index])
592 }
593
594 pub fn edge(&self, id: impl Into<EdgeId>) -> Option<&Edge> {
596 self.edge_indexes
597 .get(&id.into())
598 .map(|index| &self.edges[*index])
599 }
600
601 pub fn contains_node(&self, id: impl Into<NodeId>) -> bool {
603 self.node_indexes.contains_key(&id.into())
604 }
605
606 pub fn contains_edge(&self, id: impl Into<EdgeId>) -> bool {
608 self.edge_indexes.contains_key(&id.into())
609 }
610
611 pub(crate) fn node_index(&self, id: impl Into<NodeId>) -> Result<usize, GraphError> {
612 let id = id.into();
613 self.node_indexes
614 .get(&id)
615 .copied()
616 .ok_or(GraphError::UnknownNode(id))
617 }
618
619 pub(crate) fn node_id(&self, index: usize) -> &NodeId {
620 &self.nodes[index].id
621 }
622
623 pub(crate) fn edge_at(&self, index: usize) -> &Edge {
624 &self.edges[index]
625 }
626
627 pub(crate) fn outgoing(&self, node: usize) -> &[ArcRef] {
628 self.outgoing.row(node)
629 }
630
631 pub(crate) fn incoming(&self, node: usize) -> &[ArcRef] {
632 self.incoming.row(node)
633 }
634
635 pub(crate) fn arcs(&self, node: usize, direction: super::TraversalDirection) -> ArcIter<'_> {
636 let direction = if self.kind.is_directed() {
637 direction
638 } else {
639 super::TraversalDirection::Both
640 };
641 match direction {
642 super::TraversalDirection::Out => ArcIter::One(self.outgoing(node).iter()),
643 super::TraversalDirection::In => ArcIter::One(self.incoming(node).iter()),
644 super::TraversalDirection::Both => ArcIter::Both {
645 graph: self,
646 node,
647 outgoing: self.outgoing(node),
648 incoming: self.incoming(node),
649 outgoing_index: 0,
650 incoming_index: 0,
651 },
652 }
653 }
654}
655
656fn compare_arcs(nodes: &[Node], edges: &[Edge], left: &ArcRef, right: &ArcRef) -> Ordering {
657 nodes[left.neighbor]
658 .id
659 .cmp(&nodes[right.neighbor].id)
660 .then_with(|| {
661 edges[left.edge]
662 .graphify_key
663 .cmp(&edges[right.edge].graphify_key)
664 })
665 .then_with(|| edges[left.edge].id.cmp(&edges[right.edge].id))
666}
667
668pub(crate) enum ArcIter<'a> {
669 One(slice::Iter<'a, ArcRef>),
670 Both {
671 graph: &'a Graph,
672 node: usize,
673 outgoing: &'a [ArcRef],
674 incoming: &'a [ArcRef],
675 outgoing_index: usize,
676 incoming_index: usize,
677 },
678}
679
680impl Iterator for ArcIter<'_> {
681 type Item = ArcRef;
682
683 fn next(&mut self) -> Option<Self::Item> {
684 match self {
685 Self::One(arcs) => arcs.next().copied(),
686 Self::Both {
687 graph,
688 node,
689 outgoing,
690 incoming,
691 outgoing_index,
692 incoming_index,
693 } => {
694 while incoming
695 .get(*incoming_index)
696 .is_some_and(|arc| arc.neighbor == *node)
697 {
698 *incoming_index += 1;
699 }
700 match (outgoing.get(*outgoing_index), incoming.get(*incoming_index)) {
701 (Some(left), Some(right))
702 if compare_arcs(&graph.nodes, &graph.edges, left, right)
703 != Ordering::Greater =>
704 {
705 *outgoing_index += 1;
706 Some(*left)
707 }
708 (Some(_), Some(right)) => {
709 *incoming_index += 1;
710 Some(*right)
711 }
712 (Some(left), None) => {
713 *outgoing_index += 1;
714 Some(*left)
715 }
716 (None, Some(right)) => {
717 *incoming_index += 1;
718 Some(*right)
719 }
720 (None, None) => None,
721 }
722 }
723 }
724 }
725}
726
727#[cfg(test)]
728mod tests {
729 use super::*;
730 use crate::TraversalDirection;
731
732 #[test]
733 fn construction_sorts_records_and_builds_directional_adjacency() {
734 let graph = Graph::new(
735 GraphKind::DiGraph,
736 [Node::new("b"), Node::new("a")],
737 [Edge::new("edge", "a", "b")],
738 )
739 .unwrap();
740
741 assert_eq!(
742 graph
743 .nodes()
744 .iter()
745 .map(|node| node.id.clone())
746 .collect::<Vec<_>>(),
747 ["a", "b"]
748 );
749 assert_eq!(graph.outgoing(graph.node_index("a").unwrap()).len(), 1);
750 assert_eq!(graph.incoming(graph.node_index("b").unwrap()).len(), 1);
751 }
752
753 #[test]
754 fn construction_rejects_invalid_identity_endpoint_and_weight_states() {
755 assert!(Graph::new(GraphKind::DiGraph, [Node::new("")], []).is_ok());
756 assert!(matches!(
757 Graph::new(
758 GraphKind::DiGraph,
759 [Node::new("a"), Node::new("a")],
760 []
761 ),
762 Err(GraphError::DuplicateNode(id)) if id == "a"
763 ));
764 assert!(matches!(
765 Graph::new(
766 GraphKind::DiGraph,
767 [Node::new("a")],
768 [Edge::new("ab", "a", "b")]
769 ),
770 Err(GraphError::MissingEndpoint { endpoint: "target", node_id, .. }) if node_id == "b"
771 ));
772 assert!(matches!(
773 Graph::new(
774 GraphKind::DiGraph,
775 [Node::new("a")],
776 [Edge::new("aa", "a", "a").with_weight(f64::NAN)]
777 ),
778 Err(GraphError::InvalidWeight { .. })
779 ));
780 assert_eq!(
781 Graph::new(
782 GraphKind::DiGraph,
783 [Node::new("a")],
784 [Edge::new("", "a", "a")]
785 )
786 .unwrap_err(),
787 GraphError::EmptyEdgeId
788 );
789 assert!(matches!(
790 Graph::new(
791 GraphKind::DiGraph,
792 [Node::new("a")],
793 [Edge::new("aa", "missing", "a")]
794 ),
795 Err(GraphError::MissingEndpoint {
796 endpoint: "source",
797 ..
798 })
799 ));
800 assert!(matches!(
801 Graph::new(
802 GraphKind::DiGraph,
803 [Node::new("a")],
804 [Edge::new("aa", "a", "a"), Edge::new("aa", "a", "a")]
805 ),
806 Err(GraphError::DuplicateEdge(id)) if id == EdgeId::from("aa")
807 ));
808 }
809
810 #[test]
811 fn undirected_arc_iteration_does_not_duplicate_self_loops() {
812 let graph = Graph::new(
813 GraphKind::Graph,
814 [Node::new("a"), Node::new("b")],
815 [Edge::new("aa", "a", "a"), Edge::new("ab", "a", "b")],
816 )
817 .unwrap();
818 let a = graph.node_index("a").unwrap();
819 assert_eq!(
820 graph
821 .arcs(a, super::super::TraversalDirection::Both)
822 .count(),
823 2
824 );
825 }
826
827 #[test]
828 fn bidirectional_arc_iteration_merges_incoming_and_outgoing_stably() {
829 let graph = Graph::new(
830 GraphKind::DiGraph,
831 [
832 Node::new("a"),
833 Node::new("b"),
834 Node::new("c"),
835 Node::new("d"),
836 ],
837 [
838 Edge::new("ca", "c", "a"),
839 Edge::new("ab", "a", "b"),
840 Edge::new("da", "d", "a"),
841 ],
842 )
843 .unwrap();
844 let neighbors = graph
845 .arcs(graph.node_index("a").unwrap(), TraversalDirection::Both)
846 .map(|arc| graph.node_id(arc.neighbor).clone())
847 .collect::<Vec<_>>();
848 assert_eq!(neighbors, ["b", "c", "d"]);
849 }
850
851 #[test]
852 fn constrained_float_types_reject_invalid_states_during_decode() {
853 assert!(PositiveFiniteF64::new(1.0).is_ok());
854 assert!(PositiveFiniteF64::new(0.0).is_err());
855 assert!(NonNegativeFiniteF64::new(0.0).is_ok());
856 assert!(NonNegativeFiniteF64::new(-1.0).is_err());
857 assert!(serde_json::from_str::<PositiveFiniteF64>("null").is_err());
858 assert_eq!(f64::from(PositiveFiniteF64::try_from(2.0).unwrap()), 2.0);
859 assert_eq!(f64::from(NonNegativeFiniteF64::try_from(0.5).unwrap()), 0.5);
860 }
861
862 #[test]
863 fn structural_edge_ids_round_trip_and_keep_user_strings_distinct() {
864 let original = EdgeId::original("reverse#1(edge)");
865 let reverse = EdgeId::original("edge").reversed().unwrap();
866 assert_ne!(original, reverse);
867 assert_eq!(reverse.to_string(), "reverse#1(edge)");
868 assert_eq!(
869 EdgeId::synthesized_reverse("edge", u64::MAX)
870 .unwrap()
871 .reversed(),
872 None
873 );
874 assert!(EdgeId::synthesized_reverse("edge", 0).is_none());
875 for edge_id in [
876 original,
877 reverse,
878 EdgeId::synthesized_reverse("edge", 42).unwrap(),
879 ] {
880 let encoded = serde_json::to_vec(&edge_id).unwrap();
881 assert_eq!(serde_json::from_slice::<EdgeId>(&encoded).unwrap(), edge_id);
882 }
883 }
884
885 #[test]
886 fn graph_kind_is_declared_and_simple_kinds_reject_parallel_edges() {
887 for (kind, directed, multigraph) in [
888 (GraphKind::Graph, false, false),
889 (GraphKind::DiGraph, true, false),
890 (GraphKind::MultiGraph, false, true),
891 (GraphKind::MultiDiGraph, true, true),
892 ] {
893 let encoded = serde_json::to_vec(&kind).unwrap();
894 assert_eq!(serde_json::from_slice::<GraphKind>(&encoded).unwrap(), kind);
895 assert_eq!(kind.is_directed(), directed);
896 assert_eq!(kind.is_multigraph(), multigraph);
897 }
898 let directed = Graph::new(
899 GraphKind::DiGraph,
900 [Node::new("a"), Node::new("b")],
901 [Edge::new("ab", "a", "b"), Edge::new("ba", "b", "a")],
902 )
903 .unwrap();
904 assert!(!directed.is_multigraph());
905 assert!(matches!(
906 Graph::new(
907 GraphKind::DiGraph,
908 [Node::new("a"), Node::new("b")],
909 [Edge::new("ab", "a", "b"), Edge::new("ab2", "a", "b")]
910 ),
911 Err(GraphError::ParallelEdge { .. })
912 ));
913 let multigraph = Graph::new(GraphKind::MultiGraph, [Node::new("a")], []).unwrap();
914 assert!(multigraph.is_multigraph());
915 assert!(!multigraph.is_directed());
916 }
917}