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