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