Skip to main content

graphrecords_core/graphrecord/
mod.rs

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    /// # Safety
394    ///
395    /// This function should only be used if the data has been validated against the schema.
396    /// Using this function with invalid data may lead to undefined behavior.
397    /// This function does not run any plugin hooks.
398    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    // TODO: Add tests
519    #[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    // TODO: Add tests
574    #[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    // TODO: Add tests
611    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    // TODO: Add tests
671    #[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    // TODO: Add tests
740    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    // TODO: Add tests
786    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    // TODO: Add tests
1108    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        // Adding an edge pointing to a non-existing node should fail
1254        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        // Adding an edge from a non-existing should fail
1264        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        // Querying a non-existing node should fail
1529        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        // Accessing the node attributes of a non-existing node should fail
1560        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        // Querying a non-existing node should fail
1631        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        // Accessing the edge attributes of a non-existing edge should fail
1658        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        // Accessing the edge endpoints of a non-existing edge should fail
1683        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        // Removing a non-existing node should fail
1850        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        // Adding an edge pointing to a non-existing node should fail
1928        assert!(
1929            graphrecord
1930                .add_edge("0".into(), "50".into(), HashMap::new())
1931                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1932        );
1933
1934        // Adding an edge from a non-existing node should fail
1935        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        // Removing a non-existing edge should fail
1968        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        // Adding a group with a non-existing node should fail
2035        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        // Adding a group with a non-existing edge should fail
2042        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        // Adding an already existing group should fail
2051        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        // Removing a non-existing group should fail
2097        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        // Adding a non-existing node to a group should fail
2152        assert!(
2153            graphrecord
2154                .add_node_to_group("0".into(), "50".into())
2155                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2156        );
2157
2158        // Adding a node to a group that already is in the group should fail
2159        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        // Adding a non-existing edge to a group should fail
2227        assert!(
2228            graphrecord
2229                .add_edge_to_group("0".into(), 50)
2230                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2231        );
2232
2233        // Adding an edge to a group that already is in the group should fail
2234        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        // Removing a node from a non-existing group should fail
2287        assert!(
2288            graphrecord
2289                .remove_node_from_group(&"50".into(), &"0".into())
2290                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2291        );
2292
2293        // Removing a non-existing node from a group should fail
2294        assert!(
2295            graphrecord
2296                .remove_node_from_group(&"0".into(), &"50".into())
2297                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2298        );
2299
2300        // Removing a node from a group it is not in should fail
2301        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        // Removing an edge from a non-existing group should fail
2332        assert!(
2333            graphrecord
2334                .remove_edge_from_group(&"50".into(), &0)
2335                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2336        );
2337
2338        // Removing a non-existing edge from a group should fail
2339        assert!(
2340            graphrecord
2341                .remove_edge_from_group(&"0".into(), &50)
2342                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2343        );
2344
2345        // Removing an edge from a group it is not in should fail
2346        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        // Querying a non-existing group should fail
2384        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        // Querying a non-existing group should fail
2411        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        // Queyring the groups of a non-existing node should fail
2434        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        // Queyring the groups of a non-existing edge should fail
2457        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        // Querying neighbors of a non-existing node sohuld fail
2545        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}