1pub mod attributes;
2pub mod datatypes;
3mod graph;
4mod group_mapping;
5pub mod overview;
6#[cfg(feature = "plugins")]
7pub mod plugins;
8mod polars;
9pub mod querying;
10pub mod schema;
11
12pub use self::{
13 datatypes::{GraphRecordAttribute, GraphRecordValue},
14 graph::{Attributes, EdgeIndex, NodeIndex},
15 group_mapping::Group,
16};
17use crate::errors::GraphRecordResult;
18#[cfg(feature = "plugins")]
19use crate::graphrecord::plugins::PluginName;
20use crate::{
21 errors::GraphRecordError,
22 graphrecord::{
23 attributes::{EdgeAttributesMut, NodeAttributesMut},
24 overview::{DEFAULT_TRUNCATE_DETAILS, GroupOverview, Overview},
25 polars::DataFramesExport,
26 },
27};
28use ::polars::frame::DataFrame;
29use graph::Graph;
30#[cfg(feature = "plugins")]
31use graphrecords_utils::aliases::GrHashMap;
32use graphrecords_utils::aliases::GrHashSet;
33use group_mapping::GroupMapping;
34#[cfg(feature = "plugins")]
35pub use plugins::PluginGraphRecord;
36use polars::{dataframe_to_edges, dataframe_to_nodes};
37use querying::{
38 ReturnOperand, Selection, edges::EdgeOperand, nodes::NodeOperand, wrapper::Wrapper,
39};
40use schema::{GroupSchema, Schema, SchemaType};
41#[cfg(feature = "serde")]
42use serde::{Deserialize, Serialize};
43use std::{
44 collections::{HashMap, hash_map::Entry},
45 fmt::{Display, Formatter},
46 mem,
47};
48#[cfg(feature = "serde")]
49use std::{fs, path::Path};
50
51#[derive(Debug, Clone)]
52pub struct NodeDataFrameInput {
53 pub dataframe: DataFrame,
54 pub index_column: String,
55}
56
57#[derive(Debug, Clone)]
58pub struct EdgeDataFrameInput {
59 pub dataframe: DataFrame,
60 pub source_index_column: String,
61 pub target_index_column: String,
62}
63
64impl<D, S> From<(D, S)> for NodeDataFrameInput
65where
66 D: Into<DataFrame>,
67 S: Into<String>,
68{
69 fn from(val: (D, S)) -> Self {
70 Self {
71 dataframe: val.0.into(),
72 index_column: val.1.into(),
73 }
74 }
75}
76
77impl<D, S> From<(D, S, S)> for EdgeDataFrameInput
78where
79 D: Into<DataFrame>,
80 S: Into<String>,
81{
82 fn from(val: (D, S, S)) -> Self {
83 Self {
84 dataframe: val.0.into(),
85 source_index_column: val.1.into(),
86 target_index_column: val.2.into(),
87 }
88 }
89}
90
91fn node_dataframes_to_tuples(
92 nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
93) -> GraphRecordResult<Vec<(NodeIndex, Attributes)>> {
94 let nodes = nodes_dataframes
95 .into_iter()
96 .map(|dataframe_input| {
97 let dataframe_input = dataframe_input.into();
98
99 dataframe_to_nodes(dataframe_input.dataframe, &dataframe_input.index_column)
100 })
101 .collect::<GraphRecordResult<Vec<_>>>()?
102 .into_iter()
103 .flatten()
104 .collect();
105
106 Ok(nodes)
107}
108
109#[allow(clippy::type_complexity)]
110fn dataframes_to_tuples(
111 nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
112 edges_dataframes: impl IntoIterator<Item = impl Into<EdgeDataFrameInput>>,
113) -> GraphRecordResult<(
114 Vec<(NodeIndex, Attributes)>,
115 Vec<(NodeIndex, NodeIndex, Attributes)>,
116)> {
117 let nodes = node_dataframes_to_tuples(nodes_dataframes)?;
118
119 let edges = edges_dataframes
120 .into_iter()
121 .map(|dataframe_input| {
122 let dataframe_input = dataframe_input.into();
123
124 dataframe_to_edges(
125 dataframe_input.dataframe,
126 &dataframe_input.source_index_column,
127 &dataframe_input.target_index_column,
128 )
129 })
130 .collect::<GraphRecordResult<Vec<_>>>()?
131 .into_iter()
132 .flatten()
133 .collect();
134
135 Ok((nodes, edges))
136}
137
138#[derive(Default, Debug, Clone)]
139#[allow(clippy::unsafe_derive_deserialize)]
140#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
141pub struct GraphRecord {
142 graph: Graph,
143 group_mapping: GroupMapping,
144 schema: Schema,
145}
146
147impl Display for GraphRecord {
148 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
149 let overview = Overview::new(self, Some(DEFAULT_TRUNCATE_DETAILS))
150 .map_err(|_| std::fmt::Error)?
151 .to_string();
152
153 write!(f, "{overview}")
154 }
155}
156
157impl GraphRecord {
158 #[must_use]
159 pub fn new() -> Self {
160 Self::default()
161 }
162
163 #[must_use]
164 pub fn with_schema(schema: Schema) -> Self {
165 Self {
166 schema,
167 ..Default::default()
168 }
169 }
170
171 #[must_use]
172 pub fn with_capacity(nodes: usize, edges: usize, schema: Option<Schema>) -> Self {
173 Self {
174 graph: Graph::with_capacity(nodes, edges),
175 schema: schema.unwrap_or_default(),
176 ..Default::default()
177 }
178 }
179
180 #[cfg(feature = "plugins")]
181 pub fn with_plugins(
182 plugins: GrHashMap<PluginName, Box<dyn plugins::Plugin>>,
183 ) -> GraphRecordResult<PluginGraphRecord> {
184 PluginGraphRecord::new(Self::default(), plugins)
185 }
186
187 pub fn from_tuples(
188 nodes: Vec<(NodeIndex, Attributes)>,
189 edges: Option<Vec<(NodeIndex, NodeIndex, Attributes)>>,
190 schema: Option<Schema>,
191 ) -> GraphRecordResult<Self> {
192 let mut graphrecord = Self::with_capacity(
193 nodes.len(),
194 edges.as_ref().map_or(0, std::vec::Vec::len),
195 schema,
196 );
197
198 for (node_index, attributes) in nodes {
199 graphrecord.add_node(node_index, attributes)?;
200 }
201
202 if let Some(edges) = edges {
203 for (source_node_index, target_node_index, attributes) in edges {
204 graphrecord.add_edge(source_node_index, target_node_index, attributes)?;
205 }
206 }
207
208 Ok(graphrecord)
209 }
210
211 pub fn from_dataframes(
212 nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
213 edges_dataframes: impl IntoIterator<Item = impl Into<EdgeDataFrameInput>>,
214 schema: Option<Schema>,
215 ) -> GraphRecordResult<Self> {
216 let (nodes, edges) = dataframes_to_tuples(nodes_dataframes, edges_dataframes)?;
217
218 Self::from_tuples(nodes, Some(edges), schema)
219 }
220
221 pub fn from_nodes_dataframes(
222 nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
223 schema: Option<Schema>,
224 ) -> GraphRecordResult<Self> {
225 let nodes = node_dataframes_to_tuples(nodes_dataframes)?;
226
227 Self::from_tuples(nodes, None, schema)
228 }
229
230 #[cfg(feature = "serde")]
231 pub fn from_ron<P>(path: P) -> GraphRecordResult<Self>
232 where
233 P: AsRef<Path>,
234 {
235 let file = fs::read_to_string(&path)
236 .map_err(|_| GraphRecordError::ConversionError("Failed to read file".to_string()))?;
237
238 ron::from_str(&file).map_err(|_| {
239 GraphRecordError::ConversionError(
240 "Failed to create GraphRecord from contents from file".to_string(),
241 )
242 })
243 }
244
245 #[cfg(feature = "serde")]
246 pub fn to_ron<P>(&self, path: P) -> GraphRecordResult<()>
247 where
248 P: AsRef<Path>,
249 {
250 let ron_string = ron::to_string(self).map_err(|_| {
251 GraphRecordError::ConversionError("Failed to convert GraphRecord to ron".to_string())
252 })?;
253
254 if let Some(parent) = path.as_ref().parent() {
255 fs::create_dir_all(parent).map_err(|_| {
256 GraphRecordError::ConversionError(
257 "Failed to create folders to GraphRecord save path".to_string(),
258 )
259 })?;
260 }
261
262 fs::write(&path, ron_string).map_err(|_| {
263 GraphRecordError::ConversionError(
264 "Failed to save GraphRecord due to file error".to_string(),
265 )
266 })
267 }
268
269 pub fn to_dataframes(&self) -> GraphRecordResult<DataFramesExport> {
270 DataFramesExport::new(self)
271 }
272
273 #[allow(clippy::too_many_lines)]
274 pub fn set_schema(&mut self, mut schema: Schema) -> GraphRecordResult<()> {
275 let mut nodes_group_cache = HashMap::<&Group, usize>::new();
276 let mut nodes_ungrouped_visited = false;
277 let mut edges_group_cache = HashMap::<&Group, usize>::new();
278 let mut edges_ungrouped_visited = false;
279
280 for (node_index, node) in &self.graph.nodes {
281 #[expect(clippy::missing_panics_doc, reason = "infallible")]
282 let groups_of_node: Vec<_> = self
283 .groups_of_node(node_index)
284 .expect("groups of node must exist")
285 .collect();
286
287 if groups_of_node.is_empty() {
288 match schema.schema_type() {
289 SchemaType::Inferred => {
290 let nodes_in_groups = self.group_mapping.nodes_in_group.len();
291
292 let nodes_not_in_groups = self.graph.node_count() - nodes_in_groups;
293
294 schema.update_node(
295 &node.attributes,
296 None,
297 nodes_not_in_groups == 0 || !nodes_ungrouped_visited,
298 );
299
300 nodes_ungrouped_visited = true;
301 }
302 SchemaType::Provided => {
303 schema.validate_node(node_index, &node.attributes, None)?;
304 }
305 }
306 } else {
307 for group in groups_of_node {
308 match schema.schema_type() {
309 SchemaType::Inferred => match nodes_group_cache.entry(group) {
310 Entry::Occupied(entry) => {
311 schema.update_node(
312 &node.attributes,
313 Some(group),
314 *entry.get() == 0,
315 );
316 }
317 Entry::Vacant(entry) => {
318 entry.insert(
319 self.group_mapping
320 .nodes_in_group
321 .get(group)
322 .map_or(0, GrHashSet::len),
323 );
324
325 schema.update_node(&node.attributes, Some(group), true);
326 }
327 },
328 SchemaType::Provided => {
329 schema.validate_node(node_index, &node.attributes, Some(group))?;
330 }
331 }
332 }
333 }
334 }
335
336 for (edge_index, edge) in &self.graph.edges {
337 #[expect(clippy::missing_panics_doc, reason = "infallible")]
338 let groups_of_edge: Vec<_> = self
339 .groups_of_edge(edge_index)
340 .expect("groups of edge must exist")
341 .collect();
342
343 if groups_of_edge.is_empty() {
344 match schema.schema_type() {
345 SchemaType::Inferred => {
346 let edges_in_groups = self.group_mapping.edges_in_group.len();
347
348 let edges_not_in_groups = self.graph.edge_count() - edges_in_groups;
349
350 schema.update_edge(
351 &edge.attributes,
352 None,
353 edges_not_in_groups == 0 || !edges_ungrouped_visited,
354 );
355
356 edges_ungrouped_visited = true;
357 }
358 SchemaType::Provided => {
359 schema.validate_edge(edge_index, &edge.attributes, None)?;
360 }
361 }
362 } else {
363 for group in groups_of_edge {
364 match schema.schema_type() {
365 SchemaType::Inferred => match edges_group_cache.entry(group) {
366 Entry::Occupied(entry) => {
367 schema.update_edge(
368 &edge.attributes,
369 Some(group),
370 *entry.get() == 0,
371 );
372 }
373 Entry::Vacant(entry) => {
374 entry.insert(
375 self.group_mapping
376 .edges_in_group
377 .get(group)
378 .map_or(0, GrHashSet::len),
379 );
380
381 schema.update_edge(&edge.attributes, Some(group), true);
382 }
383 },
384 SchemaType::Provided => {
385 schema.validate_edge(edge_index, &edge.attributes, Some(group))?;
386 }
387 }
388 }
389 }
390 }
391
392 mem::swap(&mut self.schema, &mut schema);
393
394 Ok(())
395 }
396
397 pub const unsafe fn set_schema_unchecked(&mut self, schema: &mut Schema) {
403 mem::swap(&mut self.schema, schema);
404 }
405
406 #[must_use]
407 pub const fn get_schema(&self) -> &Schema {
408 &self.schema
409 }
410
411 pub const fn freeze_schema(&mut self) {
412 self.schema.freeze();
413 }
414
415 pub const fn unfreeze_schema(&mut self) {
416 self.schema.unfreeze();
417 }
418
419 pub fn node_indices(&self) -> impl Iterator<Item = &NodeIndex> {
420 self.graph.node_indices()
421 }
422
423 pub fn node_attributes(&self, node_index: &NodeIndex) -> GraphRecordResult<&Attributes> {
424 self.graph
425 .node_attributes(node_index)
426 .map_err(GraphRecordError::from)
427 }
428
429 pub fn node_attributes_mut<'a>(
430 &'a mut self,
431 node_index: &'a NodeIndex,
432 ) -> GraphRecordResult<NodeAttributesMut<'a>> {
433 NodeAttributesMut::new(node_index, self)
434 }
435
436 pub fn outgoing_edges(
437 &self,
438 node_index: &NodeIndex,
439 ) -> GraphRecordResult<impl Iterator<Item = &EdgeIndex> + use<'_>> {
440 self.graph
441 .outgoing_edges(node_index)
442 .map_err(GraphRecordError::from)
443 }
444
445 pub fn incoming_edges(
446 &self,
447 node_index: &NodeIndex,
448 ) -> GraphRecordResult<impl Iterator<Item = &EdgeIndex> + use<'_>> {
449 self.graph
450 .incoming_edges(node_index)
451 .map_err(GraphRecordError::from)
452 }
453
454 pub fn edge_indices(&self) -> impl Iterator<Item = &EdgeIndex> {
455 self.graph.edge_indices()
456 }
457
458 pub fn edge_attributes(&self, edge_index: &EdgeIndex) -> GraphRecordResult<&Attributes> {
459 self.graph
460 .edge_attributes(edge_index)
461 .map_err(GraphRecordError::from)
462 }
463
464 pub fn edge_attributes_mut<'a>(
465 &'a mut self,
466 edge_index: &'a EdgeIndex,
467 ) -> GraphRecordResult<EdgeAttributesMut<'a>> {
468 EdgeAttributesMut::new(edge_index, self)
469 }
470
471 pub fn edge_endpoints(
472 &self,
473 edge_index: &EdgeIndex,
474 ) -> GraphRecordResult<(&NodeIndex, &NodeIndex)> {
475 self.graph
476 .edge_endpoints(edge_index)
477 .map_err(GraphRecordError::from)
478 }
479
480 pub fn edges_connecting<'a>(
481 &'a self,
482 outgoing_node_indices: Vec<&'a NodeIndex>,
483 incoming_node_indices: Vec<&'a NodeIndex>,
484 ) -> impl Iterator<Item = &'a EdgeIndex> + 'a {
485 self.graph
486 .edges_connecting(outgoing_node_indices, incoming_node_indices)
487 }
488
489 pub fn edges_connecting_undirected<'a>(
490 &'a self,
491 first_node_indices: Vec<&'a NodeIndex>,
492 second_node_indices: Vec<&'a NodeIndex>,
493 ) -> impl Iterator<Item = &'a EdgeIndex> + 'a {
494 self.graph
495 .edges_connecting_undirected(first_node_indices, second_node_indices)
496 }
497
498 pub fn add_node(
499 &mut self,
500 node_index: NodeIndex,
501 attributes: Attributes,
502 ) -> GraphRecordResult<()> {
503 match self.schema.schema_type() {
504 SchemaType::Inferred => {
505 let nodes_in_groups = self.group_mapping.nodes_in_group.len();
506
507 let nodes_not_in_groups = self.graph.node_count() - nodes_in_groups;
508
509 self.schema
510 .update_node(&attributes, None, nodes_not_in_groups == 0);
511 }
512 SchemaType::Provided => {
513 self.schema.validate_node(&node_index, &attributes, None)?;
514 }
515 }
516
517 self.graph
518 .add_node(node_index, attributes)
519 .map_err(GraphRecordError::from)
520 }
521
522 #[allow(clippy::needless_pass_by_value)]
524 pub fn add_node_with_group(
525 &mut self,
526 node_index: NodeIndex,
527 attributes: Attributes,
528 group: Group,
529 ) -> GraphRecordResult<()> {
530 match self.schema.schema_type() {
531 SchemaType::Inferred => {
532 let nodes_in_group = self
533 .group_mapping
534 .nodes_in_group
535 .get(&group)
536 .map_or(0, GrHashSet::len);
537
538 self.schema
539 .update_node(&attributes, Some(&group), nodes_in_group == 0);
540 }
541 SchemaType::Provided => {
542 self.schema
543 .validate_node(&node_index, &attributes, Some(&group))?;
544 }
545 }
546
547 self.graph
548 .add_node(node_index.clone(), attributes)
549 .map_err(GraphRecordError::from)?;
550
551 self.group_mapping
552 .add_node_to_group(group, node_index.clone())
553 .inspect_err(|_| {
554 #[expect(clippy::missing_panics_doc, reason = "infallible")]
555 self.graph
556 .remove_node(&node_index, &mut self.group_mapping)
557 .expect("Node must exist");
558 })
559 }
560
561 pub fn remove_node(&mut self, node_index: &NodeIndex) -> GraphRecordResult<Attributes> {
562 self.group_mapping.remove_node(node_index);
563
564 self.graph
565 .remove_node(node_index, &mut self.group_mapping)
566 .map_err(GraphRecordError::from)
567 }
568
569 pub fn add_nodes(&mut self, nodes: Vec<(NodeIndex, Attributes)>) -> GraphRecordResult<()> {
570 for (node_index, attributes) in nodes {
571 self.add_node(node_index, attributes)?;
572 }
573
574 Ok(())
575 }
576
577 #[allow(clippy::needless_pass_by_value)]
579 pub fn add_nodes_with_group(
580 &mut self,
581 nodes: Vec<(NodeIndex, Attributes)>,
582 group: Group,
583 ) -> GraphRecordResult<()> {
584 if !self.contains_group(&group) {
585 self.add_group(group.clone(), None, None)?;
586 }
587
588 for (node_index, attributes) in nodes {
589 self.add_node_with_group(node_index, attributes, group.clone())?;
590 }
591
592 Ok(())
593 }
594
595 pub fn add_nodes_dataframes(
596 &mut self,
597 nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
598 ) -> GraphRecordResult<()> {
599 let nodes = nodes_dataframes
600 .into_iter()
601 .map(|dataframe_input| {
602 let dataframe_input = dataframe_input.into();
603
604 dataframe_to_nodes(dataframe_input.dataframe, &dataframe_input.index_column)
605 })
606 .collect::<Result<Vec<_>, _>>()?
607 .into_iter()
608 .flatten()
609 .collect();
610
611 self.add_nodes(nodes)
612 }
613
614 pub fn add_nodes_dataframes_with_group(
616 &mut self,
617 nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
618 group: Group,
619 ) -> GraphRecordResult<()> {
620 let nodes = nodes_dataframes
621 .into_iter()
622 .map(|dataframe_input| {
623 let dataframe_input = dataframe_input.into();
624
625 dataframe_to_nodes(dataframe_input.dataframe, &dataframe_input.index_column)
626 })
627 .collect::<Result<Vec<_>, _>>()?
628 .into_iter()
629 .flatten()
630 .collect();
631
632 self.add_nodes_with_group(nodes, group)
633 }
634
635 #[allow(clippy::needless_pass_by_value)]
636 pub fn add_edge(
637 &mut self,
638 source_node_index: NodeIndex,
639 target_node_index: NodeIndex,
640 attributes: Attributes,
641 ) -> GraphRecordResult<EdgeIndex> {
642 let edge_index = self
643 .graph
644 .add_edge(source_node_index, target_node_index, attributes.clone())
645 .map_err(GraphRecordError::from)?;
646
647 match self.schema.schema_type() {
648 SchemaType::Inferred => {
649 let edges_in_groups = self.group_mapping.edges_in_group.len();
650
651 let edges_not_in_groups = self.graph.edge_count() - edges_in_groups;
652
653 self.schema
654 .update_edge(&attributes, None, edges_not_in_groups <= 1);
655
656 Ok(edge_index)
657 }
658 SchemaType::Provided => {
659 match self.schema.validate_edge(&edge_index, &attributes, None) {
660 Ok(()) => Ok(edge_index),
661 Err(e) => {
662 #[expect(clippy::missing_panics_doc, reason = "infallible")]
663 self.graph
664 .remove_edge(&edge_index)
665 .expect("Edge must exist");
666
667 Err(e.into())
668 }
669 }
670 }
671 }
672 }
673
674 #[allow(clippy::needless_pass_by_value)]
676 pub fn add_edge_with_group(
677 &mut self,
678 source_node_index: NodeIndex,
679 target_node_index: NodeIndex,
680 attributes: Attributes,
681 group: Group,
682 ) -> GraphRecordResult<EdgeIndex> {
683 let edge_index = self
684 .graph
685 .add_edge(source_node_index, target_node_index, attributes.clone())
686 .map_err(GraphRecordError::from)?;
687
688 match self.schema.schema_type() {
689 SchemaType::Inferred => {
690 let edges_in_group = self
691 .group_mapping
692 .edges_in_group
693 .get(&group)
694 .map_or(0, GrHashSet::len);
695
696 self.schema
697 .update_edge(&attributes, Some(&group), edges_in_group == 0);
698 }
699 SchemaType::Provided => {
700 self.schema
701 .validate_edge(&edge_index, &attributes, Some(&group))
702 .inspect_err(|_| {
703 #[expect(clippy::missing_panics_doc, reason = "infallible")]
704 self.graph
705 .remove_edge(&edge_index)
706 .expect("Edge must exist");
707 })?;
708 }
709 }
710
711 self.group_mapping
712 .add_edge_to_group(group, edge_index)
713 .inspect_err(|_| {
714 #[expect(clippy::missing_panics_doc, reason = "infallible")]
715 self.graph
716 .remove_edge(&edge_index)
717 .expect("Edge must exist");
718 })?;
719
720 Ok(edge_index)
721 }
722
723 pub fn remove_edge(&mut self, edge_index: &EdgeIndex) -> GraphRecordResult<Attributes> {
724 self.group_mapping.remove_edge(edge_index);
725
726 self.graph
727 .remove_edge(edge_index)
728 .map_err(GraphRecordError::from)
729 }
730
731 pub fn add_edges(
732 &mut self,
733 edges: Vec<(NodeIndex, NodeIndex, Attributes)>,
734 ) -> GraphRecordResult<Vec<EdgeIndex>> {
735 edges
736 .into_iter()
737 .map(|(source_node_index, target_node_index, attributes)| {
738 self.add_edge(source_node_index, target_node_index, attributes)
739 })
740 .collect()
741 }
742
743 pub fn add_edges_with_group(
745 &mut self,
746 edges: Vec<(NodeIndex, NodeIndex, Attributes)>,
747 group: &Group,
748 ) -> GraphRecordResult<Vec<EdgeIndex>> {
749 if !self.contains_group(group) {
750 self.add_group(group.clone(), None, None)?;
751 }
752
753 edges
754 .into_iter()
755 .map(|(source_node_index, target_node_index, attributes)| {
756 self.add_edge_with_group(
757 source_node_index,
758 target_node_index,
759 attributes,
760 group.clone(),
761 )
762 })
763 .collect()
764 }
765
766 pub fn add_edges_dataframes(
767 &mut self,
768 edges_dataframes: impl IntoIterator<Item = impl Into<EdgeDataFrameInput>>,
769 ) -> GraphRecordResult<Vec<EdgeIndex>> {
770 let edges = edges_dataframes
771 .into_iter()
772 .map(|dataframe_input| {
773 let dataframe_input = dataframe_input.into();
774
775 dataframe_to_edges(
776 dataframe_input.dataframe,
777 &dataframe_input.source_index_column,
778 &dataframe_input.target_index_column,
779 )
780 })
781 .collect::<Result<Vec<_>, _>>()?
782 .into_iter()
783 .flatten()
784 .collect();
785
786 self.add_edges(edges)
787 }
788
789 pub fn add_edges_dataframes_with_group(
791 &mut self,
792 edges_dataframes: impl IntoIterator<Item = impl Into<EdgeDataFrameInput>>,
793 group: &Group,
794 ) -> GraphRecordResult<Vec<EdgeIndex>> {
795 let edges = edges_dataframes
796 .into_iter()
797 .map(|dataframe_input| {
798 let dataframe_input = dataframe_input.into();
799
800 dataframe_to_edges(
801 dataframe_input.dataframe,
802 &dataframe_input.source_index_column,
803 &dataframe_input.target_index_column,
804 )
805 })
806 .collect::<Result<Vec<_>, _>>()?
807 .into_iter()
808 .flatten()
809 .collect();
810
811 self.add_edges_with_group(edges, group)
812 }
813
814 pub fn add_group(
815 &mut self,
816 group: Group,
817 node_indices: Option<Vec<NodeIndex>>,
818 edge_indices: Option<Vec<EdgeIndex>>,
819 ) -> GraphRecordResult<()> {
820 if self.group_mapping.contains_group(&group) {
821 return Err(GraphRecordError::AssertionError(format!(
822 "Group {group} already exists"
823 )));
824 }
825
826 if let Some(ref node_indices) = node_indices {
827 for node_index in node_indices {
828 if !self.graph.contains_node(node_index) {
829 return Err(GraphRecordError::IndexError(format!(
830 "Cannot find node with index {node_index}",
831 )));
832 }
833 }
834 }
835
836 if let Some(ref edge_indices) = edge_indices {
837 for edge_index in edge_indices {
838 if !self.graph.contains_edge(edge_index) {
839 return Err(GraphRecordError::IndexError(format!(
840 "Cannot find edge with index {edge_index}",
841 )));
842 }
843 }
844 }
845
846 match self.schema.schema_type() {
847 SchemaType::Inferred => {
848 if !self.schema.groups().contains_key(&group) {
849 self.schema
850 .add_group(group.clone(), GroupSchema::default())?;
851 }
852
853 if let Some(ref node_indices) = node_indices {
854 let mut empty = true;
855
856 for node_index in node_indices {
857 let node_attributes = self.graph.node_attributes(node_index)?;
858
859 self.schema
860 .update_node(node_attributes, Some(&group), empty);
861
862 empty = false;
863 }
864 }
865
866 if let Some(ref edge_indices) = edge_indices {
867 let mut empty = true;
868
869 for edge_index in edge_indices {
870 let edge_attributes = self.graph.edge_attributes(edge_index)?;
871
872 self.schema
873 .update_edge(edge_attributes, Some(&group), empty);
874
875 empty = false;
876 }
877 }
878 }
879 SchemaType::Provided => {
880 if !self.schema.groups().contains_key(&group) {
881 return Err(GraphRecordError::SchemaError(format!(
882 "Group {group} is not defined in the schema"
883 )));
884 }
885
886 if let Some(ref node_indices) = node_indices {
887 for node_index in node_indices {
888 let node_attributes = self.graph.node_attributes(node_index)?;
889
890 self.schema
891 .validate_node(node_index, node_attributes, Some(&group))?;
892 }
893 }
894
895 if let Some(ref edge_indices) = edge_indices {
896 for edge_index in edge_indices {
897 let edge_attributes = self.graph.edge_attributes(edge_index)?;
898
899 self.schema
900 .validate_edge(edge_index, edge_attributes, Some(&group))?;
901 }
902 }
903 }
904 }
905
906 #[expect(clippy::missing_panics_doc, reason = "infallible")]
907 self.group_mapping
908 .add_group(group, node_indices, edge_indices)
909 .expect("Group must not exist");
910
911 Ok(())
912 }
913
914 pub fn remove_group(&mut self, group: &Group) -> GraphRecordResult<()> {
915 self.group_mapping.remove_group(group)
916 }
917
918 pub fn add_node_to_group(
919 &mut self,
920 group: Group,
921 node_index: NodeIndex,
922 ) -> GraphRecordResult<()> {
923 let node_attributes = self.graph.node_attributes(&node_index)?;
924
925 match self.schema.schema_type() {
926 SchemaType::Inferred => {
927 let nodes_in_group = self
928 .group_mapping
929 .nodes_in_group
930 .get(&group)
931 .map_or(0, GrHashSet::len);
932
933 self.schema
934 .update_node(node_attributes, Some(&group), nodes_in_group == 0);
935 }
936 SchemaType::Provided => {
937 self.schema
938 .validate_node(&node_index, node_attributes, Some(&group))?;
939 }
940 }
941
942 self.group_mapping.add_node_to_group(group, node_index)
943 }
944
945 pub fn add_edge_to_group(
946 &mut self,
947 group: Group,
948 edge_index: EdgeIndex,
949 ) -> GraphRecordResult<()> {
950 let edge_attributes = self.graph.edge_attributes(&edge_index)?;
951
952 match self.schema.schema_type() {
953 SchemaType::Inferred => {
954 let edges_in_group = self
955 .group_mapping
956 .edges_in_group
957 .get(&group)
958 .map_or(0, GrHashSet::len);
959
960 self.schema
961 .update_edge(edge_attributes, Some(&group), edges_in_group == 0);
962 }
963 SchemaType::Provided => {
964 self.schema
965 .validate_edge(&edge_index, edge_attributes, Some(&group))?;
966 }
967 }
968
969 self.group_mapping.add_edge_to_group(group, edge_index)
970 }
971
972 pub fn remove_node_from_group(
973 &mut self,
974 group: &Group,
975 node_index: &NodeIndex,
976 ) -> GraphRecordResult<()> {
977 if !self.graph.contains_node(node_index) {
978 return Err(GraphRecordError::IndexError(format!(
979 "Cannot find node with index {node_index}",
980 )));
981 }
982
983 self.group_mapping.remove_node_from_group(group, node_index)
984 }
985
986 pub fn remove_edge_from_group(
987 &mut self,
988 group: &Group,
989 edge_index: &EdgeIndex,
990 ) -> GraphRecordResult<()> {
991 if !self.graph.contains_edge(edge_index) {
992 return Err(GraphRecordError::IndexError(format!(
993 "Cannot find edge with index {edge_index}",
994 )));
995 }
996
997 self.group_mapping.remove_edge_from_group(group, edge_index)
998 }
999
1000 pub fn groups(&self) -> impl Iterator<Item = &Group> {
1001 self.group_mapping.groups()
1002 }
1003
1004 pub fn nodes_in_group(
1005 &self,
1006 group: &Group,
1007 ) -> GraphRecordResult<impl Iterator<Item = &NodeIndex> + use<'_>> {
1008 self.group_mapping.nodes_in_group(group)
1009 }
1010
1011 pub fn ungrouped_nodes(&self) -> impl Iterator<Item = &NodeIndex> {
1012 let nodes_in_groups: GrHashSet<_> = self
1013 .groups()
1014 .flat_map(|group| {
1015 #[expect(clippy::missing_panics_doc, reason = "infallible")]
1016 self.nodes_in_group(group).expect("Group must exist")
1017 })
1018 .collect();
1019
1020 self.graph
1021 .node_indices()
1022 .filter(move |node_index| !nodes_in_groups.contains(*node_index))
1023 }
1024
1025 pub fn edges_in_group(
1026 &self,
1027 group: &Group,
1028 ) -> GraphRecordResult<impl Iterator<Item = &EdgeIndex> + use<'_>> {
1029 self.group_mapping.edges_in_group(group)
1030 }
1031
1032 pub fn ungrouped_edges(&self) -> impl Iterator<Item = &EdgeIndex> {
1033 let edges_in_groups: GrHashSet<_> = self
1034 .groups()
1035 .flat_map(|group| {
1036 #[expect(clippy::missing_panics_doc, reason = "infallible")]
1037 self.edges_in_group(group).expect("Group must exist")
1038 })
1039 .collect();
1040
1041 self.graph
1042 .edge_indices()
1043 .filter(move |edge_index| !edges_in_groups.contains(*edge_index))
1044 }
1045
1046 pub fn groups_of_node(
1047 &self,
1048 node_index: &NodeIndex,
1049 ) -> GraphRecordResult<impl Iterator<Item = &Group> + use<'_>> {
1050 if !self.graph.contains_node(node_index) {
1051 return Err(GraphRecordError::IndexError(format!(
1052 "Cannot find node with index {node_index}",
1053 )));
1054 }
1055
1056 Ok(self.group_mapping.groups_of_node(node_index))
1057 }
1058
1059 pub fn groups_of_edge(
1060 &self,
1061 edge_index: &EdgeIndex,
1062 ) -> GraphRecordResult<impl Iterator<Item = &Group> + use<'_>> {
1063 if !self.graph.contains_edge(edge_index) {
1064 return Err(GraphRecordError::IndexError(format!(
1065 "Cannot find edge with index {edge_index}",
1066 )));
1067 }
1068
1069 Ok(self.group_mapping.groups_of_edge(edge_index))
1070 }
1071
1072 #[must_use]
1073 pub fn node_count(&self) -> usize {
1074 self.graph.node_count()
1075 }
1076
1077 #[must_use]
1078 pub fn edge_count(&self) -> usize {
1079 self.graph.edge_count()
1080 }
1081
1082 #[must_use]
1083 pub fn group_count(&self) -> usize {
1084 self.group_mapping.group_count()
1085 }
1086
1087 #[must_use]
1088 pub fn contains_node(&self, node_index: &NodeIndex) -> bool {
1089 self.graph.contains_node(node_index)
1090 }
1091
1092 #[must_use]
1093 pub fn contains_edge(&self, edge_index: &EdgeIndex) -> bool {
1094 self.graph.contains_edge(edge_index)
1095 }
1096
1097 #[must_use]
1098 pub fn contains_group(&self, group: &Group) -> bool {
1099 self.group_mapping.contains_group(group)
1100 }
1101
1102 pub fn neighbors_outgoing(
1103 &self,
1104 node_index: &NodeIndex,
1105 ) -> GraphRecordResult<impl Iterator<Item = &NodeIndex> + use<'_>> {
1106 self.graph
1107 .neighbors_outgoing(node_index)
1108 .map_err(GraphRecordError::from)
1109 }
1110
1111 pub fn neighbors_incoming(
1113 &self,
1114 node_index: &NodeIndex,
1115 ) -> GraphRecordResult<impl Iterator<Item = &NodeIndex> + use<'_>> {
1116 self.graph
1117 .neighbors_incoming(node_index)
1118 .map_err(GraphRecordError::from)
1119 }
1120
1121 pub fn neighbors_undirected(
1122 &self,
1123 node_index: &NodeIndex,
1124 ) -> GraphRecordResult<impl Iterator<Item = &NodeIndex> + use<'_>> {
1125 self.graph
1126 .neighbors_undirected(node_index)
1127 .map_err(GraphRecordError::from)
1128 }
1129
1130 pub fn clear(&mut self) {
1131 self.graph.clear();
1132 self.group_mapping.clear();
1133 }
1134
1135 pub fn query_nodes<'a, Q, R>(&'a self, query: Q) -> Selection<'a, R>
1136 where
1137 Q: FnOnce(&Wrapper<NodeOperand>) -> R,
1138 R: ReturnOperand<'a>,
1139 {
1140 Selection::new_node(self, query)
1141 }
1142
1143 pub fn query_edges<'a, Q, R>(&'a self, query: Q) -> Selection<'a, R>
1144 where
1145 Q: FnOnce(&Wrapper<EdgeOperand>) -> R,
1146 R: ReturnOperand<'a>,
1147 {
1148 Selection::new_edge(self, query)
1149 }
1150
1151 pub fn overview(&self, truncate_details: Option<usize>) -> GraphRecordResult<Overview> {
1152 Overview::new(self, truncate_details)
1153 }
1154
1155 pub fn group_overview(
1156 &self,
1157 group: &Group,
1158 truncate_details: Option<usize>,
1159 ) -> GraphRecordResult<GroupOverview> {
1160 GroupOverview::new(self, Some(group), truncate_details)
1161 }
1162}
1163
1164#[cfg(test)]
1165mod test {
1166 use super::{Attributes, GraphRecord, GraphRecordAttribute, NodeIndex};
1167 use crate::{
1168 errors::GraphRecordError,
1169 graphrecord::{
1170 SchemaType,
1171 datatypes::DataType,
1172 schema::{AttributeSchema, GroupSchema, Schema},
1173 },
1174 };
1175 use polars::prelude::{DataFrame, NamedFrom, PolarsError, Series};
1176 use std::collections::HashMap;
1177 #[cfg(feature = "serde")]
1178 use std::fs;
1179
1180 fn create_nodes() -> Vec<(NodeIndex, Attributes)> {
1181 vec![
1182 (
1183 "0".into(),
1184 HashMap::from([("lorem".into(), "ipsum".into())]),
1185 ),
1186 (
1187 "1".into(),
1188 HashMap::from([("amet".into(), "consectetur".into())]),
1189 ),
1190 (
1191 "2".into(),
1192 HashMap::from([("adipiscing".into(), "elit".into())]),
1193 ),
1194 ("3".into(), HashMap::new()),
1195 ]
1196 }
1197
1198 fn create_edges() -> Vec<(NodeIndex, NodeIndex, Attributes)> {
1199 vec![
1200 (
1201 "0".into(),
1202 "1".into(),
1203 HashMap::from([
1204 ("sed".into(), "do".into()),
1205 ("eiusmod".into(), "tempor".into()),
1206 ]),
1207 ),
1208 (
1209 "1".into(),
1210 "0".into(),
1211 HashMap::from([
1212 ("sed".into(), "do".into()),
1213 ("eiusmod".into(), "tempor".into()),
1214 ]),
1215 ),
1216 (
1217 "1".into(),
1218 "2".into(),
1219 HashMap::from([("incididunt".into(), "ut".into())]),
1220 ),
1221 ("0".into(), "2".into(), HashMap::new()),
1222 ]
1223 }
1224
1225 fn create_nodes_dataframe() -> Result<DataFrame, PolarsError> {
1226 let s0 = Series::new("index".into(), &["0", "1"]);
1227 let s1 = Series::new("attribute".into(), &[1, 2]);
1228 DataFrame::new(2, vec![s0.into(), s1.into()])
1229 }
1230
1231 fn create_edges_dataframe() -> Result<DataFrame, PolarsError> {
1232 let s0 = Series::new("from".into(), &["0", "1"]);
1233 let s1 = Series::new("to".into(), &["1", "0"]);
1234 let s2 = Series::new("attribute".into(), &[1, 2]);
1235 DataFrame::new(2, vec![s0.into(), s1.into(), s2.into()])
1236 }
1237
1238 fn create_graphrecord() -> GraphRecord {
1239 let nodes = create_nodes();
1240 let edges = create_edges();
1241
1242 GraphRecord::from_tuples(nodes, Some(edges), None).unwrap()
1243 }
1244
1245 #[test]
1246 fn test_from_tuples() {
1247 let graphrecord = create_graphrecord();
1248
1249 assert_eq!(4, graphrecord.node_count());
1250 assert_eq!(4, graphrecord.edge_count());
1251 }
1252
1253 #[test]
1254 fn test_invalid_from_tuples() {
1255 let nodes = create_nodes();
1256
1257 assert!(
1259 GraphRecord::from_tuples(
1260 nodes.clone(),
1261 Some(vec![("0".into(), "50".into(), HashMap::new())]),
1262 None
1263 )
1264 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1265 );
1266
1267 assert!(
1269 GraphRecord::from_tuples(
1270 nodes,
1271 Some(vec![("50".into(), "0".into(), HashMap::new())]),
1272 None
1273 )
1274 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1275 );
1276 }
1277
1278 #[test]
1279 fn test_from_dataframes() {
1280 let nodes_dataframe = create_nodes_dataframe().unwrap();
1281 let edges_dataframe = create_edges_dataframe().unwrap();
1282
1283 let graphrecord = GraphRecord::from_dataframes(
1284 vec![(nodes_dataframe, "index".to_string())],
1285 vec![(edges_dataframe, "from".to_string(), "to".to_string())],
1286 None,
1287 )
1288 .unwrap();
1289
1290 assert_eq!(2, graphrecord.node_count());
1291 assert_eq!(2, graphrecord.edge_count());
1292 }
1293
1294 #[test]
1295 fn test_from_nodes_dataframes() {
1296 let nodes_dataframe = create_nodes_dataframe().unwrap();
1297
1298 let graphrecord =
1299 GraphRecord::from_nodes_dataframes(vec![(nodes_dataframe, "index".to_string())], None)
1300 .unwrap();
1301
1302 assert_eq!(2, graphrecord.node_count());
1303 }
1304
1305 #[test]
1306 #[cfg(feature = "serde")]
1307 fn test_ron() {
1308 let graphrecord = create_graphrecord();
1309
1310 let mut file_path = std::env::temp_dir().into_os_string();
1311 file_path.push("/graphrecord_test/");
1312
1313 fs::create_dir_all(&file_path).unwrap();
1314
1315 file_path.push("test.ron");
1316
1317 graphrecord.to_ron(&file_path).unwrap();
1318
1319 let loaded_graphrecord = GraphRecord::from_ron(&file_path).unwrap();
1320
1321 assert_eq!(graphrecord.node_count(), loaded_graphrecord.node_count());
1322 assert_eq!(graphrecord.edge_count(), loaded_graphrecord.edge_count());
1323 }
1324
1325 #[test]
1326 fn test_set_schema() {
1327 let mut graphrecord = GraphRecord::new();
1328
1329 let group_schema = GroupSchema::new(
1330 AttributeSchema::from([("attribute".into(), DataType::Int.into())]),
1331 AttributeSchema::from([("attribute".into(), DataType::Int.into())]),
1332 );
1333
1334 graphrecord
1335 .add_node("0".into(), HashMap::from([("attribute".into(), 1.into())]))
1336 .unwrap();
1337 graphrecord
1338 .add_node("1".into(), HashMap::from([("attribute".into(), 1.into())]))
1339 .unwrap();
1340 graphrecord
1341 .add_edge(
1342 "0".into(),
1343 "1".into(),
1344 HashMap::from([("attribute".into(), 1.into())]),
1345 )
1346 .unwrap();
1347
1348 let schema = Schema::new_provided(HashMap::default(), group_schema.clone());
1349
1350 assert!(graphrecord.set_schema(schema.clone()).is_ok());
1351
1352 assert_eq!(schema, *graphrecord.get_schema());
1353
1354 let mut graphrecord = GraphRecord::new();
1355
1356 graphrecord
1357 .add_node("0".into(), HashMap::from([("attribute".into(), 1.into())]))
1358 .unwrap();
1359 graphrecord
1360 .add_node("1".into(), HashMap::from([("attribute".into(), 1.into())]))
1361 .unwrap();
1362 graphrecord
1363 .add_node("2".into(), HashMap::from([("attribute".into(), 1.into())]))
1364 .unwrap();
1365 graphrecord
1366 .add_edge(
1367 "0".into(),
1368 "1".into(),
1369 HashMap::from([("attribute".into(), 1.into())]),
1370 )
1371 .unwrap();
1372 graphrecord
1373 .add_edge(
1374 "0".into(),
1375 "1".into(),
1376 HashMap::from([("attribute".into(), 1.into())]),
1377 )
1378 .unwrap();
1379 graphrecord
1380 .add_edge(
1381 "0".into(),
1382 "1".into(),
1383 HashMap::from([("attribute".into(), 1.into())]),
1384 )
1385 .unwrap();
1386
1387 let schema = Schema::new_inferred(
1388 HashMap::from([
1389 ("0".into(), group_schema.clone()),
1390 ("1".into(), group_schema.clone()),
1391 ]),
1392 group_schema,
1393 );
1394
1395 graphrecord
1396 .add_group(
1397 "0".into(),
1398 Some(vec!["0".into(), "1".into()]),
1399 Some(vec![0, 1]),
1400 )
1401 .unwrap();
1402 graphrecord
1403 .add_group(
1404 "1".into(),
1405 Some(vec!["0".into(), "1".into()]),
1406 Some(vec![0, 1]),
1407 )
1408 .unwrap();
1409
1410 let inferred_schema = Schema::new_inferred(HashMap::default(), GroupSchema::default());
1411
1412 assert!(graphrecord.set_schema(inferred_schema).is_ok());
1413
1414 assert_eq!(schema, *graphrecord.get_schema());
1415 }
1416
1417 #[test]
1418 fn test_invalid_set_schema() {
1419 let mut graphrecord = GraphRecord::new();
1420
1421 graphrecord
1422 .add_node("0".into(), HashMap::from([("attribute2".into(), 1.into())]))
1423 .unwrap();
1424
1425 let schema = Schema::new_provided(
1426 HashMap::default(),
1427 GroupSchema::new(
1428 AttributeSchema::from([("attribute".into(), DataType::Int.into())]),
1429 AttributeSchema::from([("attribute".into(), DataType::Int.into())]),
1430 ),
1431 );
1432
1433 let previous_schema = graphrecord.get_schema().clone();
1434
1435 assert!(
1436 graphrecord
1437 .set_schema(schema.clone())
1438 .is_err_and(|e| { matches!(e, GraphRecordError::SchemaError(_)) })
1439 );
1440
1441 assert_eq!(previous_schema, *graphrecord.get_schema());
1442
1443 let mut graphrecord = GraphRecord::new();
1444
1445 graphrecord
1446 .add_node("0".into(), HashMap::from([("attribute".into(), 1.into())]))
1447 .unwrap();
1448 graphrecord
1449 .add_node("1".into(), HashMap::from([("attribute".into(), 1.into())]))
1450 .unwrap();
1451 graphrecord
1452 .add_edge(
1453 "0".into(),
1454 "1".into(),
1455 HashMap::from([("attribute2".into(), 1.into())]),
1456 )
1457 .unwrap();
1458
1459 let previous_schema = graphrecord.get_schema().clone();
1460
1461 assert!(
1462 graphrecord
1463 .set_schema(schema)
1464 .is_err_and(|e| { matches!(e, GraphRecordError::SchemaError(_)) })
1465 );
1466
1467 assert_eq!(previous_schema, *graphrecord.get_schema());
1468 }
1469
1470 #[test]
1471 fn test_freeze_schema() {
1472 let mut graphrecord = GraphRecord::new();
1473
1474 assert_eq!(
1475 SchemaType::Inferred,
1476 *graphrecord.get_schema().schema_type()
1477 );
1478
1479 graphrecord.freeze_schema();
1480
1481 assert_eq!(
1482 SchemaType::Provided,
1483 *graphrecord.get_schema().schema_type()
1484 );
1485 }
1486
1487 #[test]
1488 fn test_unfreeze_schema() {
1489 let schema = Schema::new_provided(HashMap::default(), GroupSchema::default());
1490 let mut graphrecord = GraphRecord::with_schema(schema);
1491
1492 assert_eq!(
1493 *graphrecord.get_schema().schema_type(),
1494 SchemaType::Provided
1495 );
1496
1497 graphrecord.unfreeze_schema();
1498
1499 assert_eq!(
1500 *graphrecord.get_schema().schema_type(),
1501 SchemaType::Inferred
1502 );
1503 }
1504
1505 #[test]
1506 fn test_node_indices() {
1507 let graphrecord = create_graphrecord();
1508
1509 let node_indices: Vec<_> = create_nodes()
1510 .into_iter()
1511 .map(|(node_index, _)| node_index)
1512 .collect();
1513
1514 for node_index in graphrecord.node_indices() {
1515 assert!(node_indices.contains(node_index));
1516 }
1517 }
1518
1519 #[test]
1520 fn test_node_attributes() {
1521 let graphrecord = create_graphrecord();
1522
1523 let attributes = graphrecord.node_attributes(&"0".into()).unwrap();
1524
1525 assert_eq!(&create_nodes()[0].1, attributes);
1526 }
1527
1528 #[test]
1529 fn test_invalid_node_attributes() {
1530 let graphrecord = create_graphrecord();
1531
1532 assert!(
1534 graphrecord
1535 .node_attributes(&"50".into())
1536 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1537 );
1538 }
1539
1540 #[test]
1541 fn test_node_attributes_mut() {
1542 let mut graphrecord = create_graphrecord();
1543
1544 let node_index = "0".into();
1545 let mut attributes = graphrecord.node_attributes_mut(&node_index).unwrap();
1546
1547 let new_attributes = HashMap::from([("0".into(), "1".into()), ("2".into(), "3".into())]);
1548
1549 attributes
1550 .replace_attributes(new_attributes.clone())
1551 .unwrap();
1552
1553 assert_eq!(
1554 &new_attributes,
1555 graphrecord.node_attributes(&node_index).unwrap()
1556 );
1557 }
1558
1559 #[test]
1560 fn test_invalid_node_attributes_mut() {
1561 let mut graphrecord = create_graphrecord();
1562
1563 assert!(
1565 graphrecord
1566 .node_attributes_mut(&"50".into())
1567 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1568 );
1569 }
1570
1571 #[test]
1572 fn test_outgoing_edges() {
1573 let graphrecord = create_graphrecord();
1574
1575 let edges = graphrecord.outgoing_edges(&"0".into()).unwrap();
1576
1577 assert_eq!(2, edges.count());
1578 }
1579
1580 #[test]
1581 fn test_invalid_outgoing_edges() {
1582 let graphrecord = create_graphrecord();
1583
1584 assert!(
1585 graphrecord
1586 .outgoing_edges(&"50".into())
1587 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1588 );
1589 }
1590
1591 #[test]
1592 fn test_incoming_edges() {
1593 let graphrecord = create_graphrecord();
1594
1595 let edges = graphrecord.incoming_edges(&"2".into()).unwrap();
1596
1597 assert_eq!(2, edges.count());
1598 }
1599
1600 #[test]
1601 fn test_invalid_incoming_edges() {
1602 let graphrecord = create_graphrecord();
1603
1604 assert!(
1605 graphrecord
1606 .incoming_edges(&"50".into())
1607 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1608 );
1609 }
1610
1611 #[test]
1612 fn test_edge_indices() {
1613 let graphrecord = create_graphrecord();
1614 let edges = [0, 1, 2, 3];
1615
1616 for edge in graphrecord.edge_indices() {
1617 assert!(edges.contains(edge));
1618 }
1619 }
1620
1621 #[test]
1622 fn test_edge_attributes() {
1623 let graphrecord = create_graphrecord();
1624
1625 let attributes = graphrecord.edge_attributes(&0).unwrap();
1626
1627 assert_eq!(&create_edges()[0].2, attributes);
1628 }
1629
1630 #[test]
1631 fn test_invalid_edge_attributes() {
1632 let graphrecord = create_graphrecord();
1633
1634 assert!(
1636 graphrecord
1637 .edge_attributes(&50)
1638 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1639 );
1640 }
1641
1642 #[test]
1643 fn test_edge_attributes_mut() {
1644 let mut graphrecord = create_graphrecord();
1645
1646 let mut attributes = graphrecord.edge_attributes_mut(&0).unwrap();
1647
1648 let new_attributes = HashMap::from([("0".into(), "1".into()), ("2".into(), "3".into())]);
1649
1650 attributes
1651 .replace_attributes(new_attributes.clone())
1652 .unwrap();
1653
1654 assert_eq!(&new_attributes, graphrecord.edge_attributes(&0).unwrap());
1655 }
1656
1657 #[test]
1658 fn test_invalid_edge_attributes_mut() {
1659 let mut graphrecord = create_graphrecord();
1660
1661 assert!(
1663 graphrecord
1664 .edge_attributes_mut(&50)
1665 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1666 );
1667 }
1668
1669 #[test]
1670 fn test_edge_endpoints() {
1671 let graphrecord = create_graphrecord();
1672
1673 let edge = &create_edges()[0];
1674
1675 let endpoints = graphrecord.edge_endpoints(&0).unwrap();
1676
1677 assert_eq!(&edge.0, endpoints.0);
1678
1679 assert_eq!(&edge.1, endpoints.1);
1680 }
1681
1682 #[test]
1683 fn test_invalid_edge_endpoints() {
1684 let graphrecord = create_graphrecord();
1685
1686 assert!(
1688 graphrecord
1689 .edge_endpoints(&50)
1690 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1691 );
1692 }
1693
1694 #[test]
1695 fn test_edges_connecting() {
1696 let graphrecord = create_graphrecord();
1697
1698 let first_index = "0".into();
1699 let second_index = "1".into();
1700 let edges_connecting =
1701 graphrecord.edges_connecting(vec![&first_index], vec![&second_index]);
1702
1703 assert_eq!(vec![&0], edges_connecting.collect::<Vec<_>>());
1704
1705 let first_index = "0".into();
1706 let second_index = "3".into();
1707 let edges_connecting =
1708 graphrecord.edges_connecting(vec![&first_index], vec![&second_index]);
1709
1710 assert_eq!(0, edges_connecting.count());
1711
1712 let first_index = "0".into();
1713 let second_index = "1".into();
1714 let third_index = "2".into();
1715 let mut edges_connecting: Vec<_> = graphrecord
1716 .edges_connecting(vec![&first_index, &second_index], vec![&third_index])
1717 .collect();
1718
1719 edges_connecting.sort();
1720 assert_eq!(vec![&2, &3], edges_connecting);
1721
1722 let first_index = "0".into();
1723 let second_index = "1".into();
1724 let third_index = "2".into();
1725 let fourth_index = "3".into();
1726 let mut edges_connecting: Vec<_> = graphrecord
1727 .edges_connecting(
1728 vec![&first_index, &second_index],
1729 vec![&third_index, &fourth_index],
1730 )
1731 .collect();
1732
1733 edges_connecting.sort();
1734 assert_eq!(vec![&2, &3], edges_connecting);
1735 }
1736
1737 #[test]
1738 fn test_edges_connecting_undirected() {
1739 let graphrecord = create_graphrecord();
1740
1741 let first_index = "0".into();
1742 let second_index = "1".into();
1743 let mut edges_connecting: Vec<_> = graphrecord
1744 .edges_connecting_undirected(vec![&first_index], vec![&second_index])
1745 .collect();
1746
1747 edges_connecting.sort();
1748 assert_eq!(vec![&0, &1], edges_connecting);
1749 }
1750
1751 #[test]
1752 fn test_add_node() {
1753 let mut graphrecord = GraphRecord::new();
1754
1755 assert_eq!(0, graphrecord.node_count());
1756
1757 graphrecord.add_node("0".into(), HashMap::new()).unwrap();
1758
1759 assert_eq!(1, graphrecord.node_count());
1760
1761 graphrecord.freeze_schema();
1762
1763 graphrecord.add_node("1".into(), HashMap::new()).unwrap();
1764
1765 assert_eq!(2, graphrecord.node_count());
1766 }
1767
1768 #[test]
1769 fn test_invalid_add_node() {
1770 let mut graphrecord = create_graphrecord();
1771
1772 assert!(
1773 graphrecord
1774 .add_node("0".into(), HashMap::new())
1775 .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
1776 );
1777
1778 graphrecord.freeze_schema();
1779
1780 assert!(
1781 graphrecord
1782 .add_node("4".into(), HashMap::from([("attribute".into(), 1.into())]))
1783 .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
1784 );
1785 }
1786
1787 #[test]
1788 fn test_remove_node() {
1789 let mut graphrecord = create_graphrecord();
1790
1791 graphrecord
1792 .add_group("group".into(), Some(vec!["0".into()]), Some(vec![0]))
1793 .unwrap();
1794
1795 let nodes = create_nodes();
1796
1797 assert_eq!(4, graphrecord.node_count());
1798 assert_eq!(4, graphrecord.edge_count());
1799 assert_eq!(
1800 1,
1801 graphrecord
1802 .nodes_in_group(&("group".into()))
1803 .unwrap()
1804 .count()
1805 );
1806 assert_eq!(
1807 1,
1808 graphrecord
1809 .edges_in_group(&("group".into()))
1810 .unwrap()
1811 .count()
1812 );
1813
1814 assert_eq!(nodes[0].1, graphrecord.remove_node(&"0".into()).unwrap());
1815
1816 assert_eq!(3, graphrecord.node_count());
1817 assert_eq!(1, graphrecord.edge_count());
1818 assert_eq!(
1819 0,
1820 graphrecord
1821 .nodes_in_group(&("group".into()))
1822 .unwrap()
1823 .count()
1824 );
1825 assert_eq!(
1826 0,
1827 graphrecord
1828 .edges_in_group(&("group".into()))
1829 .unwrap()
1830 .count()
1831 );
1832
1833 let mut graphrecord = GraphRecord::new();
1834
1835 graphrecord.add_node(0.into(), HashMap::new()).unwrap();
1836 graphrecord
1837 .add_edge(0.into(), 0.into(), HashMap::new())
1838 .unwrap();
1839
1840 assert_eq!(1, graphrecord.node_count());
1841 assert_eq!(1, graphrecord.edge_count());
1842
1843 assert!(graphrecord.remove_node(&0.into()).is_ok());
1844
1845 assert_eq!(0, graphrecord.node_count());
1846 assert_eq!(0, graphrecord.edge_count());
1847 }
1848
1849 #[test]
1850 fn test_invalid_remove_node() {
1851 let mut graphrecord = create_graphrecord();
1852
1853 assert!(
1855 graphrecord
1856 .remove_node(&"50".into())
1857 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1858 );
1859 }
1860
1861 #[test]
1862 fn test_add_nodes() {
1863 let mut graphrecord = GraphRecord::new();
1864
1865 assert_eq!(0, graphrecord.node_count());
1866
1867 let nodes = create_nodes();
1868
1869 graphrecord.add_nodes(nodes).unwrap();
1870
1871 assert_eq!(4, graphrecord.node_count());
1872 }
1873
1874 #[test]
1875 fn test_invalid_add_nodes() {
1876 let mut graphrecord = create_graphrecord();
1877
1878 let nodes = create_nodes();
1879
1880 assert!(
1881 graphrecord
1882 .add_nodes(nodes)
1883 .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
1884 );
1885 }
1886
1887 #[test]
1888 fn test_add_nodes_dataframe() {
1889 let mut graphrecord = GraphRecord::new();
1890
1891 assert_eq!(0, graphrecord.node_count());
1892
1893 let nodes_dataframe = create_nodes_dataframe().unwrap();
1894
1895 graphrecord
1896 .add_nodes_dataframes(vec![(nodes_dataframe, "index".to_string())])
1897 .unwrap();
1898
1899 assert_eq!(2, graphrecord.node_count());
1900 }
1901
1902 #[test]
1903 fn test_add_edge() {
1904 let mut graphrecord = create_graphrecord();
1905
1906 assert_eq!(4, graphrecord.edge_count());
1907
1908 graphrecord
1909 .add_edge("0".into(), "3".into(), HashMap::new())
1910 .unwrap();
1911
1912 assert_eq!(5, graphrecord.edge_count());
1913
1914 graphrecord.freeze_schema();
1915
1916 graphrecord
1917 .add_edge("0".into(), "3".into(), HashMap::new())
1918 .unwrap();
1919
1920 assert_eq!(6, graphrecord.edge_count());
1921 }
1922
1923 #[test]
1924 fn test_invalid_add_edge() {
1925 let mut graphrecord = GraphRecord::new();
1926
1927 let nodes = create_nodes();
1928
1929 graphrecord.add_nodes(nodes).unwrap();
1930
1931 assert!(
1933 graphrecord
1934 .add_edge("0".into(), "50".into(), HashMap::new())
1935 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1936 );
1937
1938 assert!(
1940 graphrecord
1941 .add_edge("50".into(), "0".into(), HashMap::new())
1942 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1943 );
1944
1945 graphrecord.freeze_schema();
1946
1947 assert!(
1948 graphrecord
1949 .add_edge(
1950 "0".into(),
1951 "3".into(),
1952 HashMap::from([("attribute".into(), 1.into())])
1953 )
1954 .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
1955 );
1956 }
1957
1958 #[test]
1959 fn test_remove_edge() {
1960 let mut graphrecord = create_graphrecord();
1961
1962 let edges = create_edges();
1963
1964 assert_eq!(edges[0].2, graphrecord.remove_edge(&0).unwrap());
1965 }
1966
1967 #[test]
1968 fn test_invalid_remove_edge() {
1969 let mut graphrecord = create_graphrecord();
1970
1971 assert!(
1973 graphrecord
1974 .remove_edge(&50)
1975 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1976 );
1977 }
1978
1979 #[test]
1980 fn test_add_edges() {
1981 let mut graphrecord = GraphRecord::new();
1982
1983 let nodes = create_nodes();
1984
1985 graphrecord.add_nodes(nodes).unwrap();
1986
1987 assert_eq!(0, graphrecord.edge_count());
1988
1989 let edges = create_edges();
1990
1991 graphrecord.add_edges(edges).unwrap();
1992
1993 assert_eq!(4, graphrecord.edge_count());
1994 }
1995
1996 #[test]
1997 fn test_add_edges_dataframe() {
1998 let mut graphrecord = GraphRecord::new();
1999
2000 let nodes = create_nodes();
2001
2002 graphrecord.add_nodes(nodes).unwrap();
2003
2004 assert_eq!(0, graphrecord.edge_count());
2005
2006 let edges = create_edges_dataframe().unwrap();
2007
2008 graphrecord
2009 .add_edges_dataframes(vec![(edges, "from", "to")])
2010 .unwrap();
2011
2012 assert_eq!(2, graphrecord.edge_count());
2013 }
2014
2015 #[test]
2016 fn test_add_group() {
2017 let mut graphrecord = create_graphrecord();
2018
2019 assert_eq!(0, graphrecord.group_count());
2020
2021 graphrecord.add_group("0".into(), None, None).unwrap();
2022
2023 assert_eq!(1, graphrecord.group_count());
2024
2025 graphrecord
2026 .add_group("1".into(), Some(vec!["0".into(), "1".into()]), None)
2027 .unwrap();
2028
2029 assert_eq!(2, graphrecord.group_count());
2030
2031 assert_eq!(2, graphrecord.nodes_in_group(&"1".into()).unwrap().count());
2032 }
2033
2034 #[test]
2035 fn test_invalid_add_group() {
2036 let mut graphrecord = create_graphrecord();
2037
2038 assert!(
2040 graphrecord
2041 .add_group("0".into(), Some(vec!["50".into()]), None)
2042 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2043 );
2044
2045 assert!(
2047 graphrecord
2048 .add_group("0".into(), None, Some(vec![50]))
2049 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2050 );
2051
2052 graphrecord.add_group("0".into(), None, None).unwrap();
2053
2054 assert!(
2056 graphrecord
2057 .add_group("0".into(), None, None)
2058 .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
2059 );
2060
2061 graphrecord.freeze_schema();
2062
2063 assert!(
2064 graphrecord
2065 .add_group("2".into(), None, None)
2066 .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2067 );
2068
2069 graphrecord.remove_group(&"0".into()).unwrap();
2070
2071 assert!(
2072 graphrecord
2073 .add_group("0".into(), Some(vec!["0".into()]), None)
2074 .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2075 );
2076 assert!(
2077 graphrecord
2078 .add_group("0".into(), None, Some(vec![0]))
2079 .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2080 );
2081 }
2082
2083 #[test]
2084 fn test_remove_group() {
2085 let mut graphrecord = create_graphrecord();
2086
2087 graphrecord.add_group("0".into(), None, None).unwrap();
2088
2089 assert_eq!(1, graphrecord.group_count());
2090
2091 graphrecord.remove_group(&"0".into()).unwrap();
2092
2093 assert_eq!(0, graphrecord.group_count());
2094 }
2095
2096 #[test]
2097 fn test_invalid_remove_group() {
2098 let mut graphrecord = GraphRecord::new();
2099
2100 assert!(
2102 graphrecord
2103 .remove_group(&"0".into())
2104 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2105 );
2106 }
2107
2108 #[test]
2109 fn test_add_node_to_group() {
2110 let mut graphrecord = create_graphrecord();
2111
2112 graphrecord
2113 .add_group("0".into(), Some(vec!["0".into(), "1".into()]), None)
2114 .unwrap();
2115
2116 assert_eq!(2, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
2117
2118 graphrecord
2119 .add_node_to_group("0".into(), "2".into())
2120 .unwrap();
2121
2122 assert_eq!(3, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
2123
2124 graphrecord
2125 .add_node("4".into(), HashMap::from([("test".into(), "test".into())]))
2126 .unwrap();
2127
2128 graphrecord
2129 .add_group("1".into(), Some(vec!["4".into()]), None)
2130 .unwrap();
2131
2132 graphrecord.freeze_schema();
2133
2134 graphrecord
2135 .add_node("5".into(), HashMap::from([("test".into(), "test".into())]))
2136 .unwrap();
2137
2138 assert!(
2139 graphrecord
2140 .add_node_to_group("1".into(), "5".into())
2141 .is_ok()
2142 );
2143
2144 assert_eq!(2, graphrecord.nodes_in_group(&"1".into()).unwrap().count());
2145 }
2146
2147 #[test]
2148 fn test_invalid_add_node_to_group() {
2149 let mut graphrecord = create_graphrecord();
2150
2151 graphrecord
2152 .add_group("0".into(), Some(vec!["0".into()]), None)
2153 .unwrap();
2154
2155 assert!(
2157 graphrecord
2158 .add_node_to_group("0".into(), "50".into())
2159 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2160 );
2161
2162 assert!(
2164 graphrecord
2165 .add_node_to_group("0".into(), "0".into())
2166 .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
2167 );
2168
2169 let mut graphrecord = GraphRecord::new();
2170
2171 graphrecord
2172 .add_node("0".into(), HashMap::from([("test".into(), "test".into())]))
2173 .unwrap();
2174 graphrecord.add_group("group".into(), None, None).unwrap();
2175
2176 graphrecord.freeze_schema();
2177
2178 assert!(
2179 graphrecord
2180 .add_node_to_group("group".into(), "0".into())
2181 .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2182 );
2183 }
2184
2185 #[test]
2186 fn test_add_edge_to_group() {
2187 let mut graphrecord = create_graphrecord();
2188
2189 graphrecord
2190 .add_group("0".into(), None, Some(vec![0, 1]))
2191 .unwrap();
2192
2193 assert_eq!(2, graphrecord.edges_in_group(&"0".into()).unwrap().count());
2194
2195 graphrecord.add_edge_to_group("0".into(), 2).unwrap();
2196
2197 assert_eq!(3, graphrecord.edges_in_group(&"0".into()).unwrap().count());
2198
2199 graphrecord
2200 .add_edge("0".into(), "1".into(), HashMap::new())
2201 .unwrap();
2202
2203 graphrecord
2204 .add_group("1".into(), None, Some(vec![3]))
2205 .unwrap();
2206
2207 graphrecord.freeze_schema();
2208
2209 let edge_index = graphrecord
2210 .add_edge("0".into(), "1".into(), HashMap::new())
2211 .unwrap();
2212
2213 assert!(
2214 graphrecord
2215 .add_edge_to_group("1".into(), edge_index)
2216 .is_ok()
2217 );
2218
2219 assert_eq!(2, graphrecord.edges_in_group(&"1".into()).unwrap().count());
2220 }
2221
2222 #[test]
2223 fn test_invalid_add_edge_to_group() {
2224 let mut graphrecord = create_graphrecord();
2225
2226 graphrecord
2227 .add_group("0".into(), None, Some(vec![0]))
2228 .unwrap();
2229
2230 assert!(
2232 graphrecord
2233 .add_edge_to_group("0".into(), 50)
2234 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2235 );
2236
2237 assert!(
2239 graphrecord
2240 .add_edge_to_group("0".into(), 0)
2241 .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
2242 );
2243
2244 let mut graphrecord = GraphRecord::new();
2245
2246 graphrecord.add_node("0".into(), HashMap::new()).unwrap();
2247 graphrecord
2248 .add_edge(
2249 "0".into(),
2250 "0".into(),
2251 HashMap::from([("test".into(), "test".into())]),
2252 )
2253 .unwrap();
2254 graphrecord.add_group("group".into(), None, None).unwrap();
2255
2256 graphrecord.freeze_schema();
2257
2258 assert!(
2259 graphrecord
2260 .add_edge_to_group("group".into(), 0)
2261 .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2262 );
2263 }
2264
2265 #[test]
2266 fn test_remove_node_from_group() {
2267 let mut graphrecord = create_graphrecord();
2268
2269 graphrecord
2270 .add_group("0".into(), Some(vec!["0".into(), "1".into()]), None)
2271 .unwrap();
2272
2273 assert_eq!(2, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
2274
2275 graphrecord
2276 .remove_node_from_group(&"0".into(), &"0".into())
2277 .unwrap();
2278
2279 assert_eq!(1, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
2280 }
2281
2282 #[test]
2283 fn test_invalid_remove_node_from_group() {
2284 let mut graphrecord = create_graphrecord();
2285
2286 graphrecord
2287 .add_group("0".into(), Some(vec!["0".into()]), None)
2288 .unwrap();
2289
2290 assert!(
2292 graphrecord
2293 .remove_node_from_group(&"50".into(), &"0".into())
2294 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2295 );
2296
2297 assert!(
2299 graphrecord
2300 .remove_node_from_group(&"0".into(), &"50".into())
2301 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2302 );
2303
2304 assert!(
2306 graphrecord
2307 .remove_node_from_group(&"0".into(), &"1".into())
2308 .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
2309 );
2310 }
2311
2312 #[test]
2313 fn test_remove_edge_from_group() {
2314 let mut graphrecord = create_graphrecord();
2315
2316 graphrecord
2317 .add_group("0".into(), None, Some(vec![0, 1]))
2318 .unwrap();
2319
2320 assert_eq!(2, graphrecord.edges_in_group(&"0".into()).unwrap().count());
2321
2322 graphrecord.remove_edge_from_group(&"0".into(), &0).unwrap();
2323
2324 assert_eq!(1, graphrecord.edges_in_group(&"0".into()).unwrap().count());
2325 }
2326
2327 #[test]
2328 fn test_invalid_remove_edge_from_group() {
2329 let mut graphrecord = create_graphrecord();
2330
2331 graphrecord
2332 .add_group("0".into(), None, Some(vec![0]))
2333 .unwrap();
2334
2335 assert!(
2337 graphrecord
2338 .remove_edge_from_group(&"50".into(), &0)
2339 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2340 );
2341
2342 assert!(
2344 graphrecord
2345 .remove_edge_from_group(&"0".into(), &50)
2346 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2347 );
2348
2349 assert!(
2351 graphrecord
2352 .remove_edge_from_group(&"0".into(), &1)
2353 .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
2354 );
2355 }
2356
2357 #[test]
2358 fn test_groups() {
2359 let mut graphrecord = create_graphrecord();
2360
2361 graphrecord.add_group("0".into(), None, None).unwrap();
2362
2363 let groups: Vec<_> = graphrecord.groups().collect();
2364
2365 assert_eq!(vec![&(GraphRecordAttribute::from("0"))], groups);
2366 }
2367
2368 #[test]
2369 fn test_nodes_in_group() {
2370 let mut graphrecord = create_graphrecord();
2371
2372 graphrecord.add_group("0".into(), None, None).unwrap();
2373
2374 assert_eq!(0, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
2375
2376 graphrecord
2377 .add_group("1".into(), Some(vec!["0".into()]), None)
2378 .unwrap();
2379
2380 assert_eq!(1, graphrecord.nodes_in_group(&"1".into()).unwrap().count());
2381 }
2382
2383 #[test]
2384 fn test_invalid_nodes_in_group() {
2385 let graphrecord = create_graphrecord();
2386
2387 assert!(
2389 graphrecord
2390 .nodes_in_group(&"0".into())
2391 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2392 );
2393 }
2394
2395 #[test]
2396 fn test_edges_in_group() {
2397 let mut graphrecord = create_graphrecord();
2398
2399 graphrecord.add_group("0".into(), None, None).unwrap();
2400
2401 assert_eq!(0, graphrecord.edges_in_group(&"0".into()).unwrap().count());
2402
2403 graphrecord
2404 .add_group("1".into(), None, Some(vec![0]))
2405 .unwrap();
2406
2407 assert_eq!(1, graphrecord.edges_in_group(&"1".into()).unwrap().count());
2408 }
2409
2410 #[test]
2411 fn test_invalid_edges_in_group() {
2412 let graphrecord = create_graphrecord();
2413
2414 assert!(
2416 graphrecord
2417 .edges_in_group(&"0".into())
2418 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2419 );
2420 }
2421
2422 #[test]
2423 fn test_groups_of_node() {
2424 let mut graphrecord = create_graphrecord();
2425
2426 graphrecord
2427 .add_group("0".into(), Some(vec!["0".into()]), None)
2428 .unwrap();
2429
2430 assert_eq!(1, graphrecord.groups_of_node(&"0".into()).unwrap().count());
2431 }
2432
2433 #[test]
2434 fn test_invalid_groups_of_node() {
2435 let graphrecord = create_graphrecord();
2436
2437 assert!(
2439 graphrecord
2440 .groups_of_node(&"50".into())
2441 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2442 );
2443 }
2444
2445 #[test]
2446 fn test_groups_of_edge() {
2447 let mut graphrecord = create_graphrecord();
2448
2449 graphrecord
2450 .add_group("0".into(), None, Some(vec![0]))
2451 .unwrap();
2452
2453 assert_eq!(1, graphrecord.groups_of_edge(&0).unwrap().count());
2454 }
2455
2456 #[test]
2457 fn test_invalid_groups_of_edge() {
2458 let graphrecord = create_graphrecord();
2459
2460 assert!(
2462 graphrecord
2463 .groups_of_edge(&50)
2464 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2465 );
2466 }
2467
2468 #[test]
2469 fn test_node_count() {
2470 let mut graphrecord = GraphRecord::new();
2471
2472 assert_eq!(0, graphrecord.node_count());
2473
2474 graphrecord.add_node("0".into(), HashMap::new()).unwrap();
2475
2476 assert_eq!(1, graphrecord.node_count());
2477 }
2478
2479 #[test]
2480 fn test_edge_count() {
2481 let mut graphrecord = GraphRecord::new();
2482
2483 graphrecord.add_node("0".into(), HashMap::new()).unwrap();
2484 graphrecord.add_node("1".into(), HashMap::new()).unwrap();
2485
2486 assert_eq!(0, graphrecord.edge_count());
2487
2488 graphrecord
2489 .add_edge("0".into(), "1".into(), HashMap::new())
2490 .unwrap();
2491
2492 assert_eq!(1, graphrecord.edge_count());
2493 }
2494
2495 #[test]
2496 fn test_group_count() {
2497 let mut graphrecord = create_graphrecord();
2498
2499 assert_eq!(0, graphrecord.group_count());
2500
2501 graphrecord.add_group("0".into(), None, None).unwrap();
2502
2503 assert_eq!(1, graphrecord.group_count());
2504 }
2505
2506 #[test]
2507 fn test_contains_node() {
2508 let graphrecord = create_graphrecord();
2509
2510 assert!(graphrecord.contains_node(&"0".into()));
2511
2512 assert!(!graphrecord.contains_node(&"50".into()));
2513 }
2514
2515 #[test]
2516 fn test_contains_edge() {
2517 let graphrecord = create_graphrecord();
2518
2519 assert!(graphrecord.contains_edge(&0));
2520
2521 assert!(!graphrecord.contains_edge(&50));
2522 }
2523
2524 #[test]
2525 fn test_contains_group() {
2526 let mut graphrecord = create_graphrecord();
2527
2528 assert!(!graphrecord.contains_group(&"0".into()));
2529
2530 graphrecord.add_group("0".into(), None, None).unwrap();
2531
2532 assert!(graphrecord.contains_group(&"0".into()));
2533 }
2534
2535 #[test]
2536 fn test_neighbors() {
2537 let graphrecord = create_graphrecord();
2538
2539 let neighbors = graphrecord.neighbors_outgoing(&"0".into()).unwrap();
2540
2541 assert_eq!(2, neighbors.count());
2542 }
2543
2544 #[test]
2545 fn test_invalid_neighbors() {
2546 let graphrecord = GraphRecord::new();
2547
2548 assert!(
2550 graphrecord
2551 .neighbors_outgoing(&"0".into())
2552 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2553 );
2554 }
2555
2556 #[test]
2557 fn test_neighbors_undirected() {
2558 let graphrecord = create_graphrecord();
2559
2560 let neighbors = graphrecord.neighbors_outgoing(&"2".into()).unwrap();
2561 assert_eq!(0, neighbors.count());
2562
2563 let neighbors = graphrecord.neighbors_undirected(&"2".into()).unwrap();
2564 assert_eq!(2, neighbors.count());
2565 }
2566
2567 #[test]
2568 fn test_invalid_neighbors_undirected() {
2569 let graphrecord = create_graphrecord();
2570
2571 assert!(
2572 graphrecord
2573 .neighbors_undirected(&"50".into())
2574 .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2575 );
2576 }
2577
2578 #[test]
2579 fn test_clear() {
2580 let mut graphrecord = create_graphrecord();
2581
2582 graphrecord.clear();
2583
2584 assert_eq!(0, graphrecord.node_count());
2585 assert_eq!(0, graphrecord.edge_count());
2586 assert_eq!(0, graphrecord.group_count());
2587 }
2588}