Skip to main content

graphrecords_core/graphrecord/
mod.rs

1pub mod attributes;
2#[cfg(feature = "connectors")]
3pub mod connector;
4pub mod datatypes;
5mod graph;
6mod group_mapping;
7pub mod overview;
8#[cfg(feature = "plugins")]
9pub mod plugins;
10mod polars;
11pub mod querying;
12pub mod schema;
13
14pub use self::{
15    datatypes::{GraphRecordAttribute, GraphRecordValue},
16    graph::{Attributes, EdgeIndex, NodeIndex},
17    group_mapping::Group,
18};
19use crate::errors::GraphRecordResult;
20#[cfg(feature = "plugins")]
21use crate::graphrecord::plugins::{Plugin, PluginName};
22use crate::{
23    errors::GraphRecordError,
24    graphrecord::{
25        attributes::{EdgeAttributesMut, NodeAttributesMut},
26        overview::{DEFAULT_TRUNCATE_DETAILS, GroupOverview, Overview},
27        polars::DataFramesExport,
28    },
29};
30use ::polars::frame::DataFrame;
31use graph::Graph;
32#[cfg(feature = "plugins")]
33use graphrecords_utils::aliases::GrHashMap;
34use graphrecords_utils::aliases::GrHashSet;
35use group_mapping::GroupMapping;
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};
43#[cfg(feature = "plugins")]
44use std::sync::Arc;
45use std::{
46    collections::{HashMap, hash_map::Entry},
47    fmt::{Display, Formatter},
48    mem,
49};
50#[cfg(feature = "serde")]
51use std::{fs, path::Path};
52
53#[derive(Debug, Clone)]
54pub struct NodeDataFrameInput {
55    pub dataframe: DataFrame,
56    pub index_column: String,
57}
58
59#[derive(Debug, Clone)]
60pub struct EdgeDataFrameInput {
61    pub dataframe: DataFrame,
62    pub source_index_column: String,
63    pub target_index_column: String,
64}
65
66impl<D, S> From<(D, S)> for NodeDataFrameInput
67where
68    D: Into<DataFrame>,
69    S: Into<String>,
70{
71    fn from(val: (D, S)) -> Self {
72        Self {
73            dataframe: val.0.into(),
74            index_column: val.1.into(),
75        }
76    }
77}
78
79impl<D, S> From<(D, S, S)> for EdgeDataFrameInput
80where
81    D: Into<DataFrame>,
82    S: Into<String>,
83{
84    fn from(val: (D, S, S)) -> Self {
85        Self {
86            dataframe: val.0.into(),
87            source_index_column: val.1.into(),
88            target_index_column: val.2.into(),
89        }
90    }
91}
92
93fn node_dataframes_to_tuples(
94    nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
95) -> GraphRecordResult<Vec<(NodeIndex, Attributes)>> {
96    let nodes = nodes_dataframes
97        .into_iter()
98        .map(|dataframe_input| {
99            let dataframe_input = dataframe_input.into();
100
101            dataframe_to_nodes(dataframe_input.dataframe, &dataframe_input.index_column)
102        })
103        .collect::<GraphRecordResult<Vec<_>>>()?
104        .into_iter()
105        .flatten()
106        .collect();
107
108    Ok(nodes)
109}
110
111#[allow(clippy::type_complexity)]
112fn dataframes_to_tuples(
113    nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
114    edges_dataframes: impl IntoIterator<Item = impl Into<EdgeDataFrameInput>>,
115) -> GraphRecordResult<(
116    Vec<(NodeIndex, Attributes)>,
117    Vec<(NodeIndex, NodeIndex, Attributes)>,
118)> {
119    let nodes = node_dataframes_to_tuples(nodes_dataframes)?;
120
121    let edges = edges_dataframes
122        .into_iter()
123        .map(|dataframe_input| {
124            let dataframe_input = dataframe_input.into();
125
126            dataframe_to_edges(
127                dataframe_input.dataframe,
128                &dataframe_input.source_index_column,
129                &dataframe_input.target_index_column,
130            )
131        })
132        .collect::<GraphRecordResult<Vec<_>>>()?
133        .into_iter()
134        .flatten()
135        .collect();
136
137    Ok((nodes, edges))
138}
139
140#[derive(Default, Debug, Clone)]
141#[allow(clippy::unsafe_derive_deserialize)]
142#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
143pub struct GraphRecord {
144    graph: Graph,
145    group_mapping: GroupMapping,
146    schema: Schema,
147
148    #[cfg(feature = "plugins")]
149    plugins: Arc<GrHashMap<PluginName, Box<dyn Plugin>>>,
150}
151
152impl Display for GraphRecord {
153    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
154        let overview = Overview::new(self, Some(DEFAULT_TRUNCATE_DETAILS))
155            .map_err(|_| std::fmt::Error)?
156            .to_string();
157
158        write!(f, "{overview}")
159    }
160}
161
162impl GraphRecord {
163    #[must_use]
164    pub fn new() -> Self {
165        Self::default()
166    }
167
168    #[must_use]
169    pub fn with_schema(schema: Schema) -> Self {
170        Self {
171            schema,
172            ..Default::default()
173        }
174    }
175
176    #[must_use]
177    pub fn with_capacity(nodes: usize, edges: usize, schema: Option<Schema>) -> Self {
178        Self {
179            graph: Graph::with_capacity(nodes, edges),
180            schema: schema.unwrap_or_default(),
181            ..Default::default()
182        }
183    }
184
185    pub fn from_tuples(
186        nodes: Vec<(NodeIndex, Attributes)>,
187        edges: Option<Vec<(NodeIndex, NodeIndex, Attributes)>>,
188        schema: Option<Schema>,
189    ) -> GraphRecordResult<Self> {
190        let mut graphrecord = Self::with_capacity(
191            nodes.len(),
192            edges.as_ref().map_or(0, std::vec::Vec::len),
193            schema,
194        );
195
196        for (node_index, attributes) in nodes {
197            graphrecord.add_node_impl(node_index, attributes)?;
198        }
199
200        if let Some(edges) = edges {
201            for (source_node_index, target_node_index, attributes) in edges {
202                graphrecord.add_edge_impl(source_node_index, target_node_index, attributes)?;
203            }
204        }
205
206        Ok(graphrecord)
207    }
208
209    pub fn from_dataframes(
210        nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
211        edges_dataframes: impl IntoIterator<Item = impl Into<EdgeDataFrameInput>>,
212        schema: Option<Schema>,
213    ) -> GraphRecordResult<Self> {
214        let (nodes, edges) = dataframes_to_tuples(nodes_dataframes, edges_dataframes)?;
215
216        Self::from_tuples(nodes, Some(edges), schema)
217    }
218
219    pub fn from_nodes_dataframes(
220        nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
221        schema: Option<Schema>,
222    ) -> GraphRecordResult<Self> {
223        let nodes = node_dataframes_to_tuples(nodes_dataframes)?;
224
225        Self::from_tuples(nodes, None, schema)
226    }
227
228    #[cfg(feature = "serde")]
229    pub fn from_ron<P>(path: P) -> GraphRecordResult<Self>
230    where
231        P: AsRef<Path>,
232    {
233        let file = fs::read_to_string(&path)
234            .map_err(|_| GraphRecordError::ConversionError("Failed to read file".to_string()))?;
235
236        ron::from_str(&file).map_err(|_| {
237            GraphRecordError::ConversionError(
238                "Failed to create GraphRecord from contents from file".to_string(),
239            )
240        })
241    }
242
243    #[cfg(feature = "serde")]
244    pub fn to_ron<P>(&self, path: P) -> GraphRecordResult<()>
245    where
246        P: AsRef<Path>,
247    {
248        let ron_string = ron::to_string(self).map_err(|_| {
249            GraphRecordError::ConversionError("Failed to convert GraphRecord to ron".to_string())
250        })?;
251
252        if let Some(parent) = path.as_ref().parent() {
253            fs::create_dir_all(parent).map_err(|_| {
254                GraphRecordError::ConversionError(
255                    "Failed to create folders to GraphRecord save path".to_string(),
256                )
257            })?;
258        }
259
260        fs::write(&path, ron_string).map_err(|_| {
261            GraphRecordError::ConversionError(
262                "Failed to save GraphRecord due to file error".to_string(),
263            )
264        })
265    }
266
267    pub fn to_dataframes(&self) -> GraphRecordResult<DataFramesExport> {
268        DataFramesExport::new(self)
269    }
270
271    #[allow(clippy::too_many_lines)]
272    fn set_schema_impl(&mut self, mut schema: Schema) -> GraphRecordResult<()> {
273        let mut nodes_group_cache = HashMap::<&Group, usize>::new();
274        let mut nodes_ungrouped_visited = false;
275        let mut edges_group_cache = HashMap::<&Group, usize>::new();
276        let mut edges_ungrouped_visited = false;
277
278        for (node_index, node) in &self.graph.nodes {
279            let groups_of_node: Vec<_> = self
280                .groups_of_node(node_index)
281                .expect("groups of node must exist")
282                .collect();
283
284            if groups_of_node.is_empty() {
285                match schema.schema_type() {
286                    SchemaType::Inferred => {
287                        let nodes_in_groups = self.group_mapping.nodes_in_group.len();
288
289                        let nodes_not_in_groups = self.graph.node_count() - nodes_in_groups;
290
291                        schema.update_node(
292                            &node.attributes,
293                            None,
294                            nodes_not_in_groups == 0 || !nodes_ungrouped_visited,
295                        );
296
297                        nodes_ungrouped_visited = true;
298                    }
299                    SchemaType::Provided => {
300                        schema.validate_node(node_index, &node.attributes, None)?;
301                    }
302                }
303            } else {
304                for group in groups_of_node {
305                    match schema.schema_type() {
306                        SchemaType::Inferred => match nodes_group_cache.entry(group) {
307                            Entry::Occupied(entry) => {
308                                schema.update_node(
309                                    &node.attributes,
310                                    Some(group),
311                                    *entry.get() == 0,
312                                );
313                            }
314                            Entry::Vacant(entry) => {
315                                entry.insert(
316                                    self.group_mapping
317                                        .nodes_in_group
318                                        .get(group)
319                                        .map_or(0, GrHashSet::len),
320                                );
321
322                                schema.update_node(&node.attributes, Some(group), true);
323                            }
324                        },
325                        SchemaType::Provided => {
326                            schema.validate_node(node_index, &node.attributes, Some(group))?;
327                        }
328                    }
329                }
330            }
331        }
332
333        for (edge_index, edge) in &self.graph.edges {
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    const fn freeze_schema_impl(&mut self) {
408        self.schema.freeze();
409    }
410
411    const fn unfreeze_schema_impl(&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    fn add_node_impl(
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    fn add_node_with_group_impl(
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                self.graph
551                    .remove_node(&node_index, &mut self.group_mapping)
552                    .expect("Node must exist");
553            })
554    }
555
556    fn add_node_with_groups_impl(
557        &mut self,
558        node_index: NodeIndex,
559        attributes: Attributes,
560        groups: impl AsRef<[Group]>,
561    ) -> GraphRecordResult<()> {
562        let groups = groups.as_ref();
563
564        match groups.split_first() {
565            None => self.add_node_impl(node_index, attributes),
566            Some((first, rest)) => {
567                self.add_node_with_group_impl(node_index.clone(), attributes, first.clone())?;
568
569                for group in rest {
570                    self.add_node_to_group_impl(group.clone(), node_index.clone())
571                        .inspect_err(|_| {
572                            self.graph
573                                .remove_node(&node_index, &mut self.group_mapping)
574                                .expect("Node must exist");
575                        })?;
576                }
577
578                Ok(())
579            }
580        }
581    }
582
583    fn remove_node_impl(&mut self, node_index: &NodeIndex) -> GraphRecordResult<Attributes> {
584        self.group_mapping.remove_node(node_index);
585
586        self.graph
587            .remove_node(node_index, &mut self.group_mapping)
588            .map_err(GraphRecordError::from)
589    }
590
591    fn add_nodes_impl(&mut self, nodes: Vec<(NodeIndex, Attributes)>) -> GraphRecordResult<()> {
592        for (node_index, attributes) in nodes {
593            self.add_node_impl(node_index, attributes)?;
594        }
595
596        Ok(())
597    }
598
599    // TODO: Add tests
600    #[allow(clippy::needless_pass_by_value)]
601    fn add_nodes_with_group_impl(
602        &mut self,
603        nodes: Vec<(NodeIndex, Attributes)>,
604        group: Group,
605    ) -> GraphRecordResult<()> {
606        if !self.contains_group(&group) {
607            self.add_group_impl(group.clone(), None, None)?;
608        }
609
610        for (node_index, attributes) in nodes {
611            self.add_node_with_group_impl(node_index, attributes, group.clone())?;
612        }
613
614        Ok(())
615    }
616
617    fn add_nodes_with_groups_impl(
618        &mut self,
619        nodes: Vec<(NodeIndex, Attributes)>,
620        groups: impl AsRef<[Group]>,
621    ) -> GraphRecordResult<()> {
622        let groups = groups.as_ref();
623
624        for group in groups {
625            if !self.contains_group(group) {
626                self.add_group_impl(group.clone(), None, None)?;
627            }
628        }
629
630        for (node_index, attributes) in nodes {
631            self.add_node_with_groups_impl(node_index, attributes, groups)?;
632        }
633
634        Ok(())
635    }
636
637    fn add_nodes_dataframes_impl(
638        &mut self,
639        nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
640    ) -> GraphRecordResult<()> {
641        let nodes = nodes_dataframes
642            .into_iter()
643            .map(|dataframe_input| {
644                let dataframe_input = dataframe_input.into();
645
646                dataframe_to_nodes(dataframe_input.dataframe, &dataframe_input.index_column)
647            })
648            .collect::<Result<Vec<_>, _>>()?
649            .into_iter()
650            .flatten()
651            .collect();
652
653        self.add_nodes_impl(nodes)
654    }
655
656    // TODO: Add tests
657    fn add_nodes_dataframes_with_group_impl(
658        &mut self,
659        nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
660        group: Group,
661    ) -> GraphRecordResult<()> {
662        let nodes = nodes_dataframes
663            .into_iter()
664            .map(|dataframe_input| {
665                let dataframe_input = dataframe_input.into();
666
667                dataframe_to_nodes(dataframe_input.dataframe, &dataframe_input.index_column)
668            })
669            .collect::<Result<Vec<_>, _>>()?
670            .into_iter()
671            .flatten()
672            .collect();
673
674        self.add_nodes_with_group_impl(nodes, group)
675    }
676
677    fn add_nodes_dataframes_with_groups_impl(
678        &mut self,
679        nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
680        groups: impl AsRef<[Group]>,
681    ) -> GraphRecordResult<()> {
682        let nodes = nodes_dataframes
683            .into_iter()
684            .map(|dataframe_input| {
685                let dataframe_input = dataframe_input.into();
686
687                dataframe_to_nodes(dataframe_input.dataframe, &dataframe_input.index_column)
688            })
689            .collect::<Result<Vec<_>, _>>()?
690            .into_iter()
691            .flatten()
692            .collect();
693
694        self.add_nodes_with_groups_impl(nodes, groups)
695    }
696
697    #[allow(clippy::needless_pass_by_value)]
698    fn add_edge_impl(
699        &mut self,
700        source_node_index: NodeIndex,
701        target_node_index: NodeIndex,
702        attributes: Attributes,
703    ) -> GraphRecordResult<EdgeIndex> {
704        let edge_index = self
705            .graph
706            .add_edge(source_node_index, target_node_index, attributes.clone())
707            .map_err(GraphRecordError::from)?;
708
709        match self.schema.schema_type() {
710            SchemaType::Inferred => {
711                let edges_in_groups = self.group_mapping.edges_in_group.len();
712
713                let edges_not_in_groups = self.graph.edge_count() - edges_in_groups;
714
715                self.schema
716                    .update_edge(&attributes, None, edges_not_in_groups <= 1);
717
718                Ok(edge_index)
719            }
720            SchemaType::Provided => {
721                match self.schema.validate_edge(&edge_index, &attributes, None) {
722                    Ok(()) => Ok(edge_index),
723                    Err(e) => {
724                        self.graph
725                            .remove_edge(&edge_index)
726                            .expect("Edge must exist");
727
728                        Err(e.into())
729                    }
730                }
731            }
732        }
733    }
734
735    // TODO: Add tests
736    #[allow(clippy::needless_pass_by_value)]
737    fn add_edge_with_group_impl(
738        &mut self,
739        source_node_index: NodeIndex,
740        target_node_index: NodeIndex,
741        attributes: Attributes,
742        group: Group,
743    ) -> GraphRecordResult<EdgeIndex> {
744        let edge_index = self
745            .graph
746            .add_edge(source_node_index, target_node_index, attributes.clone())
747            .map_err(GraphRecordError::from)?;
748
749        match self.schema.schema_type() {
750            SchemaType::Inferred => {
751                let edges_in_group = self
752                    .group_mapping
753                    .edges_in_group
754                    .get(&group)
755                    .map_or(0, GrHashSet::len);
756
757                self.schema
758                    .update_edge(&attributes, Some(&group), edges_in_group == 0);
759            }
760            SchemaType::Provided => {
761                self.schema
762                    .validate_edge(&edge_index, &attributes, Some(&group))
763                    .inspect_err(|_| {
764                        self.graph
765                            .remove_edge(&edge_index)
766                            .expect("Edge must exist");
767                    })?;
768            }
769        }
770
771        self.group_mapping
772            .add_edge_to_group(group, edge_index)
773            .inspect_err(|_| {
774                self.graph
775                    .remove_edge(&edge_index)
776                    .expect("Edge must exist");
777            })?;
778
779        Ok(edge_index)
780    }
781
782    fn add_edge_with_groups_impl(
783        &mut self,
784        source_node_index: NodeIndex,
785        target_node_index: NodeIndex,
786        attributes: Attributes,
787        groups: impl AsRef<[Group]>,
788    ) -> GraphRecordResult<EdgeIndex> {
789        let groups = groups.as_ref();
790
791        match groups.split_first() {
792            None => self.add_edge_impl(source_node_index, target_node_index, attributes),
793            Some((first, rest)) => {
794                let edge_index = self.add_edge_with_group_impl(
795                    source_node_index,
796                    target_node_index,
797                    attributes,
798                    first.clone(),
799                )?;
800
801                for group in rest {
802                    self.add_edge_to_group_impl(group.clone(), edge_index)
803                        .inspect_err(|_| {
804                            self.graph
805                                .remove_edge(&edge_index)
806                                .expect("Edge must exist");
807                        })?;
808                }
809
810                Ok(edge_index)
811            }
812        }
813    }
814
815    #[allow(clippy::trivially_copy_pass_by_ref)]
816    fn remove_edge_impl(&mut self, edge_index: &EdgeIndex) -> GraphRecordResult<Attributes> {
817        self.group_mapping.remove_edge(edge_index);
818
819        self.graph
820            .remove_edge(edge_index)
821            .map_err(GraphRecordError::from)
822    }
823
824    fn add_edges_impl(
825        &mut self,
826        edges: Vec<(NodeIndex, NodeIndex, Attributes)>,
827    ) -> GraphRecordResult<Vec<EdgeIndex>> {
828        edges
829            .into_iter()
830            .map(|(source_node_index, target_node_index, attributes)| {
831                self.add_edge_impl(source_node_index, target_node_index, attributes)
832            })
833            .collect()
834    }
835
836    // TODO: Add tests
837    fn add_edges_with_group_impl(
838        &mut self,
839        edges: Vec<(NodeIndex, NodeIndex, Attributes)>,
840        group: &Group,
841    ) -> GraphRecordResult<Vec<EdgeIndex>> {
842        if !self.contains_group(group) {
843            self.add_group_impl(group.clone(), None, None)?;
844        }
845
846        edges
847            .into_iter()
848            .map(|(source_node_index, target_node_index, attributes)| {
849                self.add_edge_with_group_impl(
850                    source_node_index,
851                    target_node_index,
852                    attributes,
853                    group.clone(),
854                )
855            })
856            .collect()
857    }
858
859    fn add_edges_with_groups_impl(
860        &mut self,
861        edges: Vec<(NodeIndex, NodeIndex, Attributes)>,
862        groups: impl AsRef<[Group]>,
863    ) -> GraphRecordResult<Vec<EdgeIndex>> {
864        let groups = groups.as_ref();
865
866        for group in groups {
867            if !self.contains_group(group) {
868                self.add_group_impl(group.clone(), None, None)?;
869            }
870        }
871
872        edges
873            .into_iter()
874            .map(|(source_node_index, target_node_index, attributes)| {
875                self.add_edge_with_groups_impl(
876                    source_node_index,
877                    target_node_index,
878                    attributes,
879                    groups,
880                )
881            })
882            .collect()
883    }
884
885    fn add_edges_dataframes_impl(
886        &mut self,
887        edges_dataframes: impl IntoIterator<Item = impl Into<EdgeDataFrameInput>>,
888    ) -> GraphRecordResult<Vec<EdgeIndex>> {
889        let edges = edges_dataframes
890            .into_iter()
891            .map(|dataframe_input| {
892                let dataframe_input = dataframe_input.into();
893
894                dataframe_to_edges(
895                    dataframe_input.dataframe,
896                    &dataframe_input.source_index_column,
897                    &dataframe_input.target_index_column,
898                )
899            })
900            .collect::<Result<Vec<_>, _>>()?
901            .into_iter()
902            .flatten()
903            .collect();
904
905        self.add_edges_impl(edges)
906    }
907
908    // TODO: Add tests
909    fn add_edges_dataframes_with_group_impl(
910        &mut self,
911        edges_dataframes: impl IntoIterator<Item = impl Into<EdgeDataFrameInput>>,
912        group: &Group,
913    ) -> GraphRecordResult<Vec<EdgeIndex>> {
914        let edges = edges_dataframes
915            .into_iter()
916            .map(|dataframe_input| {
917                let dataframe_input = dataframe_input.into();
918
919                dataframe_to_edges(
920                    dataframe_input.dataframe,
921                    &dataframe_input.source_index_column,
922                    &dataframe_input.target_index_column,
923                )
924            })
925            .collect::<Result<Vec<_>, _>>()?
926            .into_iter()
927            .flatten()
928            .collect();
929
930        self.add_edges_with_group_impl(edges, group)
931    }
932
933    fn add_edges_dataframes_with_groups_impl(
934        &mut self,
935        edges_dataframes: impl IntoIterator<Item = impl Into<EdgeDataFrameInput>>,
936        groups: impl AsRef<[Group]>,
937    ) -> GraphRecordResult<Vec<EdgeIndex>> {
938        let edges = edges_dataframes
939            .into_iter()
940            .map(|dataframe_input| {
941                let dataframe_input = dataframe_input.into();
942
943                dataframe_to_edges(
944                    dataframe_input.dataframe,
945                    &dataframe_input.source_index_column,
946                    &dataframe_input.target_index_column,
947                )
948            })
949            .collect::<Result<Vec<_>, _>>()?
950            .into_iter()
951            .flatten()
952            .collect();
953
954        self.add_edges_with_groups_impl(edges, groups)
955    }
956
957    fn add_group_impl(
958        &mut self,
959        group: Group,
960        node_indices: Option<Vec<NodeIndex>>,
961        edge_indices: Option<Vec<EdgeIndex>>,
962    ) -> GraphRecordResult<()> {
963        if self.group_mapping.contains_group(&group) {
964            return Err(GraphRecordError::AssertionError(format!(
965                "Group {group} already exists"
966            )));
967        }
968
969        if let Some(ref node_indices) = node_indices {
970            for node_index in node_indices {
971                if !self.graph.contains_node(node_index) {
972                    return Err(GraphRecordError::IndexError(format!(
973                        "Cannot find node with index {node_index}",
974                    )));
975                }
976            }
977        }
978
979        if let Some(ref edge_indices) = edge_indices {
980            for edge_index in edge_indices {
981                if !self.graph.contains_edge(edge_index) {
982                    return Err(GraphRecordError::IndexError(format!(
983                        "Cannot find edge with index {edge_index}",
984                    )));
985                }
986            }
987        }
988
989        match self.schema.schema_type() {
990            SchemaType::Inferred => {
991                if !self.schema.groups().contains_key(&group) {
992                    self.schema
993                        .add_group(group.clone(), GroupSchema::default())?;
994                }
995
996                if let Some(ref node_indices) = node_indices {
997                    let mut empty = true;
998
999                    for node_index in node_indices {
1000                        let node_attributes = self.graph.node_attributes(node_index)?;
1001
1002                        self.schema
1003                            .update_node(node_attributes, Some(&group), empty);
1004
1005                        empty = false;
1006                    }
1007                }
1008
1009                if let Some(ref edge_indices) = edge_indices {
1010                    let mut empty = true;
1011
1012                    for edge_index in edge_indices {
1013                        let edge_attributes = self.graph.edge_attributes(edge_index)?;
1014
1015                        self.schema
1016                            .update_edge(edge_attributes, Some(&group), empty);
1017
1018                        empty = false;
1019                    }
1020                }
1021            }
1022            SchemaType::Provided => {
1023                if !self.schema.groups().contains_key(&group) {
1024                    return Err(GraphRecordError::SchemaError(format!(
1025                        "Group {group} is not defined in the schema"
1026                    )));
1027                }
1028
1029                if let Some(ref node_indices) = node_indices {
1030                    for node_index in node_indices {
1031                        let node_attributes = self.graph.node_attributes(node_index)?;
1032
1033                        self.schema
1034                            .validate_node(node_index, node_attributes, Some(&group))?;
1035                    }
1036                }
1037
1038                if let Some(ref edge_indices) = edge_indices {
1039                    for edge_index in edge_indices {
1040                        let edge_attributes = self.graph.edge_attributes(edge_index)?;
1041
1042                        self.schema
1043                            .validate_edge(edge_index, edge_attributes, Some(&group))?;
1044                    }
1045                }
1046            }
1047        }
1048
1049        self.group_mapping
1050            .add_group(group, node_indices, edge_indices)
1051            .expect("Group must not exist");
1052
1053        Ok(())
1054    }
1055
1056    fn remove_group_impl(&mut self, group: &Group) -> GraphRecordResult<()> {
1057        self.group_mapping.remove_group(group)
1058    }
1059
1060    fn add_node_to_group_impl(
1061        &mut self,
1062        group: Group,
1063        node_index: NodeIndex,
1064    ) -> GraphRecordResult<()> {
1065        let node_attributes = self.graph.node_attributes(&node_index)?;
1066
1067        match self.schema.schema_type() {
1068            SchemaType::Inferred => {
1069                let nodes_in_group = self
1070                    .group_mapping
1071                    .nodes_in_group
1072                    .get(&group)
1073                    .map_or(0, GrHashSet::len);
1074
1075                self.schema
1076                    .update_node(node_attributes, Some(&group), nodes_in_group == 0);
1077            }
1078            SchemaType::Provided => {
1079                self.schema
1080                    .validate_node(&node_index, node_attributes, Some(&group))?;
1081            }
1082        }
1083
1084        self.group_mapping.add_node_to_group(group, node_index)
1085    }
1086
1087    #[allow(clippy::needless_pass_by_value)]
1088    fn add_node_to_groups_impl(
1089        &mut self,
1090        groups: impl AsRef<[Group]>,
1091        node_index: NodeIndex,
1092    ) -> GraphRecordResult<()> {
1093        groups
1094            .as_ref()
1095            .iter()
1096            .try_for_each(|group| self.add_node_to_group_impl(group.clone(), node_index.clone()))
1097    }
1098
1099    fn add_nodes_to_groups_impl(
1100        &mut self,
1101        groups: impl AsRef<[Group]>,
1102        node_indices: Vec<NodeIndex>,
1103    ) -> GraphRecordResult<()> {
1104        let groups = groups.as_ref();
1105
1106        node_indices
1107            .into_iter()
1108            .try_for_each(|node_index| self.add_node_to_groups_impl(groups, node_index))
1109    }
1110
1111    fn add_edge_to_group_impl(
1112        &mut self,
1113        group: Group,
1114        edge_index: EdgeIndex,
1115    ) -> GraphRecordResult<()> {
1116        let edge_attributes = self.graph.edge_attributes(&edge_index)?;
1117
1118        match self.schema.schema_type() {
1119            SchemaType::Inferred => {
1120                let edges_in_group = self
1121                    .group_mapping
1122                    .edges_in_group
1123                    .get(&group)
1124                    .map_or(0, GrHashSet::len);
1125
1126                self.schema
1127                    .update_edge(edge_attributes, Some(&group), edges_in_group == 0);
1128            }
1129            SchemaType::Provided => {
1130                self.schema
1131                    .validate_edge(&edge_index, edge_attributes, Some(&group))?;
1132            }
1133        }
1134
1135        self.group_mapping.add_edge_to_group(group, edge_index)
1136    }
1137
1138    fn add_edge_to_groups_impl(
1139        &mut self,
1140        groups: impl AsRef<[Group]>,
1141        edge_index: EdgeIndex,
1142    ) -> GraphRecordResult<()> {
1143        groups
1144            .as_ref()
1145            .iter()
1146            .try_for_each(|group| self.add_edge_to_group_impl(group.clone(), edge_index))
1147    }
1148
1149    fn add_edges_to_groups_impl(
1150        &mut self,
1151        groups: impl AsRef<[Group]>,
1152        edge_indices: Vec<EdgeIndex>,
1153    ) -> GraphRecordResult<()> {
1154        let groups = groups.as_ref();
1155
1156        edge_indices
1157            .into_iter()
1158            .try_for_each(|edge_index| self.add_edge_to_groups_impl(groups, edge_index))
1159    }
1160
1161    fn remove_node_from_group_impl(
1162        &mut self,
1163        group: &Group,
1164        node_index: &NodeIndex,
1165    ) -> GraphRecordResult<()> {
1166        if !self.graph.contains_node(node_index) {
1167            return Err(GraphRecordError::IndexError(format!(
1168                "Cannot find node with index {node_index}",
1169            )));
1170        }
1171
1172        self.group_mapping.remove_node_from_group(group, node_index)
1173    }
1174
1175    fn remove_node_from_groups_impl(
1176        &mut self,
1177        groups: impl AsRef<[Group]>,
1178        node_index: &NodeIndex,
1179    ) -> GraphRecordResult<()> {
1180        groups
1181            .as_ref()
1182            .iter()
1183            .try_for_each(|group| self.remove_node_from_group_impl(group, node_index))
1184    }
1185
1186    fn remove_nodes_from_groups_impl(
1187        &mut self,
1188        groups: impl AsRef<[Group]>,
1189        node_indices: &[NodeIndex],
1190    ) -> GraphRecordResult<()> {
1191        let groups = groups.as_ref();
1192
1193        node_indices
1194            .iter()
1195            .try_for_each(|node_index| self.remove_node_from_groups_impl(groups, node_index))
1196    }
1197
1198    #[allow(clippy::trivially_copy_pass_by_ref)]
1199    fn remove_edge_from_group_impl(
1200        &mut self,
1201        group: &Group,
1202        edge_index: &EdgeIndex,
1203    ) -> GraphRecordResult<()> {
1204        if !self.graph.contains_edge(edge_index) {
1205            return Err(GraphRecordError::IndexError(format!(
1206                "Cannot find edge with index {edge_index}",
1207            )));
1208        }
1209
1210        self.group_mapping.remove_edge_from_group(group, edge_index)
1211    }
1212
1213    #[allow(clippy::trivially_copy_pass_by_ref)]
1214    fn remove_edge_from_groups_impl(
1215        &mut self,
1216        groups: impl AsRef<[Group]>,
1217        edge_index: &EdgeIndex,
1218    ) -> GraphRecordResult<()> {
1219        groups
1220            .as_ref()
1221            .iter()
1222            .try_for_each(|group| self.remove_edge_from_group_impl(group, edge_index))
1223    }
1224
1225    fn remove_edges_from_groups_impl(
1226        &mut self,
1227        groups: impl AsRef<[Group]>,
1228        edge_indices: &[EdgeIndex],
1229    ) -> GraphRecordResult<()> {
1230        let groups = groups.as_ref();
1231
1232        edge_indices
1233            .iter()
1234            .try_for_each(|edge_index| self.remove_edge_from_groups_impl(groups, edge_index))
1235    }
1236
1237    pub fn groups(&self) -> impl Iterator<Item = &Group> {
1238        self.group_mapping.groups()
1239    }
1240
1241    pub fn nodes_in_group(
1242        &self,
1243        group: &Group,
1244    ) -> GraphRecordResult<impl Iterator<Item = &NodeIndex> + use<'_>> {
1245        self.group_mapping.nodes_in_group(group)
1246    }
1247
1248    pub fn ungrouped_nodes(&self) -> impl Iterator<Item = &NodeIndex> {
1249        let nodes_in_groups: GrHashSet<_> = self
1250            .groups()
1251            .flat_map(|group| {
1252                #[expect(clippy::missing_panics_doc, reason = "infallible")]
1253                self.nodes_in_group(group).expect("Group must exist")
1254            })
1255            .collect();
1256
1257        self.graph
1258            .node_indices()
1259            .filter(move |node_index| !nodes_in_groups.contains(*node_index))
1260    }
1261
1262    pub fn edges_in_group(
1263        &self,
1264        group: &Group,
1265    ) -> GraphRecordResult<impl Iterator<Item = &EdgeIndex> + use<'_>> {
1266        self.group_mapping.edges_in_group(group)
1267    }
1268
1269    pub fn ungrouped_edges(&self) -> impl Iterator<Item = &EdgeIndex> {
1270        let edges_in_groups: GrHashSet<_> = self
1271            .groups()
1272            .flat_map(|group| {
1273                #[expect(clippy::missing_panics_doc, reason = "infallible")]
1274                self.edges_in_group(group).expect("Group must exist")
1275            })
1276            .collect();
1277
1278        self.graph
1279            .edge_indices()
1280            .filter(move |edge_index| !edges_in_groups.contains(*edge_index))
1281    }
1282
1283    pub fn groups_of_node(
1284        &self,
1285        node_index: &NodeIndex,
1286    ) -> GraphRecordResult<impl Iterator<Item = &Group> + use<'_>> {
1287        if !self.graph.contains_node(node_index) {
1288            return Err(GraphRecordError::IndexError(format!(
1289                "Cannot find node with index {node_index}",
1290            )));
1291        }
1292
1293        Ok(self.group_mapping.groups_of_node(node_index))
1294    }
1295
1296    pub fn groups_of_edge(
1297        &self,
1298        edge_index: &EdgeIndex,
1299    ) -> GraphRecordResult<impl Iterator<Item = &Group> + use<'_>> {
1300        if !self.graph.contains_edge(edge_index) {
1301            return Err(GraphRecordError::IndexError(format!(
1302                "Cannot find edge with index {edge_index}",
1303            )));
1304        }
1305
1306        Ok(self.group_mapping.groups_of_edge(edge_index))
1307    }
1308
1309    #[must_use]
1310    pub fn node_count(&self) -> usize {
1311        self.graph.node_count()
1312    }
1313
1314    #[must_use]
1315    pub fn edge_count(&self) -> usize {
1316        self.graph.edge_count()
1317    }
1318
1319    #[must_use]
1320    pub fn group_count(&self) -> usize {
1321        self.group_mapping.group_count()
1322    }
1323
1324    #[must_use]
1325    pub fn contains_node(&self, node_index: &NodeIndex) -> bool {
1326        self.graph.contains_node(node_index)
1327    }
1328
1329    #[must_use]
1330    pub fn contains_edge(&self, edge_index: &EdgeIndex) -> bool {
1331        self.graph.contains_edge(edge_index)
1332    }
1333
1334    #[must_use]
1335    pub fn contains_group(&self, group: &Group) -> bool {
1336        self.group_mapping.contains_group(group)
1337    }
1338
1339    pub fn neighbors_outgoing(
1340        &self,
1341        node_index: &NodeIndex,
1342    ) -> GraphRecordResult<impl Iterator<Item = &NodeIndex> + use<'_>> {
1343        self.graph
1344            .neighbors_outgoing(node_index)
1345            .map_err(GraphRecordError::from)
1346    }
1347
1348    // TODO: Add tests
1349    pub fn neighbors_incoming(
1350        &self,
1351        node_index: &NodeIndex,
1352    ) -> GraphRecordResult<impl Iterator<Item = &NodeIndex> + use<'_>> {
1353        self.graph
1354            .neighbors_incoming(node_index)
1355            .map_err(GraphRecordError::from)
1356    }
1357
1358    pub fn neighbors_undirected(
1359        &self,
1360        node_index: &NodeIndex,
1361    ) -> GraphRecordResult<impl Iterator<Item = &NodeIndex> + use<'_>> {
1362        self.graph
1363            .neighbors_undirected(node_index)
1364            .map_err(GraphRecordError::from)
1365    }
1366
1367    fn clear_impl(&mut self) {
1368        self.graph.clear();
1369        self.group_mapping.clear();
1370    }
1371
1372    pub fn query_nodes<'a, Q, R>(&'a self, query: Q) -> Selection<'a, R>
1373    where
1374        Q: FnOnce(&Wrapper<NodeOperand>) -> R,
1375        R: ReturnOperand<'a>,
1376    {
1377        Selection::new_node(self, query)
1378    }
1379
1380    pub fn query_edges<'a, Q, R>(&'a self, query: Q) -> Selection<'a, R>
1381    where
1382        Q: FnOnce(&Wrapper<EdgeOperand>) -> R,
1383        R: ReturnOperand<'a>,
1384    {
1385        Selection::new_edge(self, query)
1386    }
1387
1388    pub fn overview(&self, truncate_details: Option<usize>) -> GraphRecordResult<Overview> {
1389        Overview::new(self, truncate_details)
1390    }
1391
1392    pub fn group_overview(
1393        &self,
1394        group: &Group,
1395        truncate_details: Option<usize>,
1396    ) -> GraphRecordResult<GroupOverview> {
1397        GroupOverview::new(self, Some(group), truncate_details)
1398    }
1399}
1400
1401#[cfg(not(feature = "plugins"))]
1402impl GraphRecord {
1403    pub fn set_schema(&mut self, schema: Schema) -> GraphRecordResult<()> {
1404        self.set_schema_impl(schema)
1405    }
1406
1407    pub const fn freeze_schema(&mut self) -> GraphRecordResult<()> {
1408        self.freeze_schema_impl();
1409
1410        Ok(())
1411    }
1412
1413    pub const fn unfreeze_schema(&mut self) -> GraphRecordResult<()> {
1414        self.unfreeze_schema_impl();
1415
1416        Ok(())
1417    }
1418
1419    pub fn add_node(
1420        &mut self,
1421        node_index: NodeIndex,
1422        attributes: Attributes,
1423    ) -> GraphRecordResult<()> {
1424        self.add_node_impl(node_index, attributes)
1425    }
1426
1427    #[allow(clippy::needless_pass_by_value)]
1428    pub fn add_node_with_group(
1429        &mut self,
1430        node_index: NodeIndex,
1431        attributes: Attributes,
1432        group: Group,
1433    ) -> GraphRecordResult<()> {
1434        self.add_node_with_group_impl(node_index, attributes, group)
1435    }
1436
1437    pub fn add_node_with_groups(
1438        &mut self,
1439        node_index: NodeIndex,
1440        attributes: Attributes,
1441        groups: impl AsRef<[Group]>,
1442    ) -> GraphRecordResult<()> {
1443        self.add_node_with_groups_impl(node_index, attributes, groups)
1444    }
1445
1446    pub fn remove_node(&mut self, node_index: &NodeIndex) -> GraphRecordResult<Attributes> {
1447        self.remove_node_impl(node_index)
1448    }
1449
1450    pub fn add_nodes(&mut self, nodes: Vec<(NodeIndex, Attributes)>) -> GraphRecordResult<()> {
1451        self.add_nodes_impl(nodes)
1452    }
1453
1454    #[allow(clippy::needless_pass_by_value)]
1455    pub fn add_nodes_with_group(
1456        &mut self,
1457        nodes: Vec<(NodeIndex, Attributes)>,
1458        group: Group,
1459    ) -> GraphRecordResult<()> {
1460        self.add_nodes_with_group_impl(nodes, group)
1461    }
1462
1463    pub fn add_nodes_with_groups(
1464        &mut self,
1465        nodes: Vec<(NodeIndex, Attributes)>,
1466        groups: impl AsRef<[Group]>,
1467    ) -> GraphRecordResult<()> {
1468        self.add_nodes_with_groups_impl(nodes, groups)
1469    }
1470
1471    pub fn add_nodes_dataframes(
1472        &mut self,
1473        nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
1474    ) -> GraphRecordResult<()> {
1475        self.add_nodes_dataframes_impl(nodes_dataframes)
1476    }
1477
1478    pub fn add_nodes_dataframes_with_group(
1479        &mut self,
1480        nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
1481        group: Group,
1482    ) -> GraphRecordResult<()> {
1483        self.add_nodes_dataframes_with_group_impl(nodes_dataframes, group)
1484    }
1485
1486    pub fn add_nodes_dataframes_with_groups(
1487        &mut self,
1488        nodes_dataframes: impl IntoIterator<Item = impl Into<NodeDataFrameInput>>,
1489        groups: impl AsRef<[Group]>,
1490    ) -> GraphRecordResult<()> {
1491        self.add_nodes_dataframes_with_groups_impl(nodes_dataframes, groups)
1492    }
1493
1494    #[allow(clippy::needless_pass_by_value)]
1495    pub fn add_edge(
1496        &mut self,
1497        source_node_index: NodeIndex,
1498        target_node_index: NodeIndex,
1499        attributes: Attributes,
1500    ) -> GraphRecordResult<EdgeIndex> {
1501        self.add_edge_impl(source_node_index, target_node_index, attributes)
1502    }
1503
1504    #[allow(clippy::needless_pass_by_value)]
1505    pub fn add_edge_with_group(
1506        &mut self,
1507        source_node_index: NodeIndex,
1508        target_node_index: NodeIndex,
1509        attributes: Attributes,
1510        group: Group,
1511    ) -> GraphRecordResult<EdgeIndex> {
1512        self.add_edge_with_group_impl(source_node_index, target_node_index, attributes, group)
1513    }
1514
1515    pub fn add_edge_with_groups(
1516        &mut self,
1517        source_node_index: NodeIndex,
1518        target_node_index: NodeIndex,
1519        attributes: Attributes,
1520        groups: impl AsRef<[Group]>,
1521    ) -> GraphRecordResult<EdgeIndex> {
1522        self.add_edge_with_groups_impl(source_node_index, target_node_index, attributes, groups)
1523    }
1524
1525    pub fn remove_edge(&mut self, edge_index: &EdgeIndex) -> GraphRecordResult<Attributes> {
1526        self.remove_edge_impl(edge_index)
1527    }
1528
1529    pub fn add_edges(
1530        &mut self,
1531        edges: Vec<(NodeIndex, NodeIndex, Attributes)>,
1532    ) -> GraphRecordResult<Vec<EdgeIndex>> {
1533        self.add_edges_impl(edges)
1534    }
1535
1536    pub fn add_edges_with_group(
1537        &mut self,
1538        edges: Vec<(NodeIndex, NodeIndex, Attributes)>,
1539        group: &Group,
1540    ) -> GraphRecordResult<Vec<EdgeIndex>> {
1541        self.add_edges_with_group_impl(edges, group)
1542    }
1543
1544    pub fn add_edges_with_groups(
1545        &mut self,
1546        edges: Vec<(NodeIndex, NodeIndex, Attributes)>,
1547        groups: impl AsRef<[Group]>,
1548    ) -> GraphRecordResult<Vec<EdgeIndex>> {
1549        self.add_edges_with_groups_impl(edges, groups)
1550    }
1551
1552    pub fn add_edges_dataframes(
1553        &mut self,
1554        edges_dataframes: impl IntoIterator<Item = impl Into<EdgeDataFrameInput>>,
1555    ) -> GraphRecordResult<Vec<EdgeIndex>> {
1556        self.add_edges_dataframes_impl(edges_dataframes)
1557    }
1558
1559    pub fn add_edges_dataframes_with_group(
1560        &mut self,
1561        edges_dataframes: impl IntoIterator<Item = impl Into<EdgeDataFrameInput>>,
1562        group: &Group,
1563    ) -> GraphRecordResult<Vec<EdgeIndex>> {
1564        self.add_edges_dataframes_with_group_impl(edges_dataframes, group)
1565    }
1566
1567    pub fn add_edges_dataframes_with_groups(
1568        &mut self,
1569        edges_dataframes: impl IntoIterator<Item = impl Into<EdgeDataFrameInput>>,
1570        groups: impl AsRef<[Group]>,
1571    ) -> GraphRecordResult<Vec<EdgeIndex>> {
1572        self.add_edges_dataframes_with_groups_impl(edges_dataframes, groups)
1573    }
1574
1575    pub fn add_group(
1576        &mut self,
1577        group: Group,
1578        node_indices: Option<Vec<NodeIndex>>,
1579        edge_indices: Option<Vec<EdgeIndex>>,
1580    ) -> GraphRecordResult<()> {
1581        self.add_group_impl(group, node_indices, edge_indices)
1582    }
1583
1584    pub fn remove_group(&mut self, group: &Group) -> GraphRecordResult<()> {
1585        self.remove_group_impl(group)
1586    }
1587
1588    pub fn add_node_to_group(
1589        &mut self,
1590        group: Group,
1591        node_index: NodeIndex,
1592    ) -> GraphRecordResult<()> {
1593        self.add_node_to_group_impl(group, node_index)
1594    }
1595
1596    pub fn add_node_to_groups(
1597        &mut self,
1598        groups: impl AsRef<[Group]>,
1599        node_index: NodeIndex,
1600    ) -> GraphRecordResult<()> {
1601        self.add_node_to_groups_impl(groups, node_index)
1602    }
1603
1604    pub fn add_nodes_to_groups(
1605        &mut self,
1606        groups: impl AsRef<[Group]>,
1607        node_indices: Vec<NodeIndex>,
1608    ) -> GraphRecordResult<()> {
1609        self.add_nodes_to_groups_impl(groups, node_indices)
1610    }
1611
1612    pub fn add_edge_to_group(
1613        &mut self,
1614        group: Group,
1615        edge_index: EdgeIndex,
1616    ) -> GraphRecordResult<()> {
1617        self.add_edge_to_group_impl(group, edge_index)
1618    }
1619
1620    pub fn add_edge_to_groups(
1621        &mut self,
1622        groups: impl AsRef<[Group]>,
1623        edge_index: EdgeIndex,
1624    ) -> GraphRecordResult<()> {
1625        self.add_edge_to_groups_impl(groups, edge_index)
1626    }
1627
1628    pub fn add_edges_to_groups(
1629        &mut self,
1630        groups: impl AsRef<[Group]>,
1631        edge_indices: Vec<EdgeIndex>,
1632    ) -> GraphRecordResult<()> {
1633        self.add_edges_to_groups_impl(groups, edge_indices)
1634    }
1635
1636    pub fn remove_node_from_group(
1637        &mut self,
1638        group: &Group,
1639        node_index: &NodeIndex,
1640    ) -> GraphRecordResult<()> {
1641        self.remove_node_from_group_impl(group, node_index)
1642    }
1643
1644    pub fn remove_node_from_groups(
1645        &mut self,
1646        groups: impl AsRef<[Group]>,
1647        node_index: &NodeIndex,
1648    ) -> GraphRecordResult<()> {
1649        self.remove_node_from_groups_impl(groups, node_index)
1650    }
1651
1652    pub fn remove_nodes_from_groups(
1653        &mut self,
1654        groups: impl AsRef<[Group]>,
1655        node_indices: &[NodeIndex],
1656    ) -> GraphRecordResult<()> {
1657        self.remove_nodes_from_groups_impl(groups, node_indices)
1658    }
1659
1660    pub fn remove_edge_from_group(
1661        &mut self,
1662        group: &Group,
1663        edge_index: &EdgeIndex,
1664    ) -> GraphRecordResult<()> {
1665        self.remove_edge_from_group_impl(group, edge_index)
1666    }
1667
1668    pub fn remove_edge_from_groups(
1669        &mut self,
1670        groups: impl AsRef<[Group]>,
1671        edge_index: &EdgeIndex,
1672    ) -> GraphRecordResult<()> {
1673        self.remove_edge_from_groups_impl(groups, edge_index)
1674    }
1675
1676    pub fn remove_edges_from_groups(
1677        &mut self,
1678        groups: impl AsRef<[Group]>,
1679        edge_indices: &[EdgeIndex],
1680    ) -> GraphRecordResult<()> {
1681        self.remove_edges_from_groups_impl(groups, edge_indices)
1682    }
1683
1684    pub fn clear(&mut self) -> GraphRecordResult<()> {
1685        self.clear_impl();
1686
1687        Ok(())
1688    }
1689}
1690
1691#[cfg(test)]
1692mod test {
1693    use super::{
1694        Attributes, EdgeDataFrameInput, GraphRecord, GraphRecordAttribute, NodeDataFrameInput,
1695        NodeIndex,
1696    };
1697    use crate::{
1698        errors::GraphRecordError,
1699        graphrecord::{
1700            SchemaType,
1701            datatypes::DataType,
1702            schema::{AttributeSchema, GroupSchema, Schema},
1703        },
1704    };
1705    use polars::prelude::{DataFrame, NamedFrom, PolarsError, Series};
1706    use std::collections::HashMap;
1707    #[cfg(feature = "serde")]
1708    use std::fs;
1709
1710    fn create_nodes() -> Vec<(NodeIndex, Attributes)> {
1711        vec![
1712            (
1713                "0".into(),
1714                HashMap::from([("lorem".into(), "ipsum".into())]),
1715            ),
1716            (
1717                "1".into(),
1718                HashMap::from([("amet".into(), "consectetur".into())]),
1719            ),
1720            (
1721                "2".into(),
1722                HashMap::from([("adipiscing".into(), "elit".into())]),
1723            ),
1724            ("3".into(), HashMap::new()),
1725        ]
1726    }
1727
1728    fn create_edges() -> Vec<(NodeIndex, NodeIndex, Attributes)> {
1729        vec![
1730            (
1731                "0".into(),
1732                "1".into(),
1733                HashMap::from([
1734                    ("sed".into(), "do".into()),
1735                    ("eiusmod".into(), "tempor".into()),
1736                ]),
1737            ),
1738            (
1739                "1".into(),
1740                "0".into(),
1741                HashMap::from([
1742                    ("sed".into(), "do".into()),
1743                    ("eiusmod".into(), "tempor".into()),
1744                ]),
1745            ),
1746            (
1747                "1".into(),
1748                "2".into(),
1749                HashMap::from([("incididunt".into(), "ut".into())]),
1750            ),
1751            ("0".into(), "2".into(), HashMap::new()),
1752        ]
1753    }
1754
1755    fn create_nodes_dataframe() -> Result<DataFrame, PolarsError> {
1756        let s0 = Series::new("index".into(), &["0", "1"]);
1757        let s1 = Series::new("attribute".into(), &[1, 2]);
1758        DataFrame::new(2, vec![s0.into(), s1.into()])
1759    }
1760
1761    fn create_edges_dataframe() -> Result<DataFrame, PolarsError> {
1762        let s0 = Series::new("from".into(), &["0", "1"]);
1763        let s1 = Series::new("to".into(), &["1", "0"]);
1764        let s2 = Series::new("attribute".into(), &[1, 2]);
1765        DataFrame::new(2, vec![s0.into(), s1.into(), s2.into()])
1766    }
1767
1768    fn create_graphrecord() -> GraphRecord {
1769        let nodes = create_nodes();
1770        let edges = create_edges();
1771
1772        GraphRecord::from_tuples(nodes, Some(edges), None).unwrap()
1773    }
1774
1775    #[test]
1776    fn test_from_tuples() {
1777        let graphrecord = create_graphrecord();
1778
1779        assert_eq!(4, graphrecord.node_count());
1780        assert_eq!(4, graphrecord.edge_count());
1781    }
1782
1783    #[test]
1784    fn test_invalid_from_tuples() {
1785        let nodes = create_nodes();
1786
1787        // Adding an edge pointing to a non-existing node should fail
1788        assert!(
1789            GraphRecord::from_tuples(
1790                nodes.clone(),
1791                Some(vec![("0".into(), "50".into(), HashMap::new())]),
1792                None
1793            )
1794            .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1795        );
1796
1797        // Adding an edge from a non-existing should fail
1798        assert!(
1799            GraphRecord::from_tuples(
1800                nodes,
1801                Some(vec![("50".into(), "0".into(), HashMap::new())]),
1802                None
1803            )
1804            .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
1805        );
1806    }
1807
1808    #[test]
1809    fn test_from_dataframes() {
1810        let nodes_dataframe = create_nodes_dataframe().unwrap();
1811        let edges_dataframe = create_edges_dataframe().unwrap();
1812
1813        let graphrecord = GraphRecord::from_dataframes(
1814            vec![(nodes_dataframe, "index".to_string())],
1815            vec![(edges_dataframe, "from".to_string(), "to".to_string())],
1816            None,
1817        )
1818        .unwrap();
1819
1820        assert_eq!(2, graphrecord.node_count());
1821        assert_eq!(2, graphrecord.edge_count());
1822    }
1823
1824    #[test]
1825    fn test_from_nodes_dataframes() {
1826        let nodes_dataframe = create_nodes_dataframe().unwrap();
1827
1828        let graphrecord =
1829            GraphRecord::from_nodes_dataframes(vec![(nodes_dataframe, "index".to_string())], None)
1830                .unwrap();
1831
1832        assert_eq!(2, graphrecord.node_count());
1833    }
1834
1835    #[test]
1836    #[cfg(feature = "serde")]
1837    fn test_ron() {
1838        let graphrecord = create_graphrecord();
1839
1840        let mut file_path = std::env::temp_dir().into_os_string();
1841        file_path.push("/graphrecord_test/");
1842
1843        fs::create_dir_all(&file_path).unwrap();
1844
1845        file_path.push("test.ron");
1846
1847        graphrecord.to_ron(&file_path).unwrap();
1848
1849        let loaded_graphrecord = GraphRecord::from_ron(&file_path).unwrap();
1850
1851        assert_eq!(graphrecord.node_count(), loaded_graphrecord.node_count());
1852        assert_eq!(graphrecord.edge_count(), loaded_graphrecord.edge_count());
1853    }
1854
1855    #[test]
1856    fn test_set_schema() {
1857        let mut graphrecord = GraphRecord::new();
1858
1859        let group_schema = GroupSchema::new(
1860            AttributeSchema::from([("attribute".into(), DataType::Int.into())]),
1861            AttributeSchema::from([("attribute".into(), DataType::Int.into())]),
1862        );
1863
1864        graphrecord
1865            .add_node("0".into(), HashMap::from([("attribute".into(), 1.into())]))
1866            .unwrap();
1867        graphrecord
1868            .add_node("1".into(), HashMap::from([("attribute".into(), 1.into())]))
1869            .unwrap();
1870        graphrecord
1871            .add_edge(
1872                "0".into(),
1873                "1".into(),
1874                HashMap::from([("attribute".into(), 1.into())]),
1875            )
1876            .unwrap();
1877
1878        let schema = Schema::new_provided(HashMap::default(), group_schema.clone());
1879
1880        assert!(graphrecord.set_schema(schema.clone()).is_ok());
1881
1882        assert_eq!(schema, *graphrecord.get_schema());
1883
1884        let mut graphrecord = GraphRecord::new();
1885
1886        graphrecord
1887            .add_node("0".into(), HashMap::from([("attribute".into(), 1.into())]))
1888            .unwrap();
1889        graphrecord
1890            .add_node("1".into(), HashMap::from([("attribute".into(), 1.into())]))
1891            .unwrap();
1892        graphrecord
1893            .add_node("2".into(), HashMap::from([("attribute".into(), 1.into())]))
1894            .unwrap();
1895        graphrecord
1896            .add_edge(
1897                "0".into(),
1898                "1".into(),
1899                HashMap::from([("attribute".into(), 1.into())]),
1900            )
1901            .unwrap();
1902        graphrecord
1903            .add_edge(
1904                "0".into(),
1905                "1".into(),
1906                HashMap::from([("attribute".into(), 1.into())]),
1907            )
1908            .unwrap();
1909        graphrecord
1910            .add_edge(
1911                "0".into(),
1912                "1".into(),
1913                HashMap::from([("attribute".into(), 1.into())]),
1914            )
1915            .unwrap();
1916
1917        let schema = Schema::new_inferred(
1918            HashMap::from([
1919                ("0".into(), group_schema.clone()),
1920                ("1".into(), group_schema.clone()),
1921            ]),
1922            group_schema,
1923        );
1924
1925        graphrecord
1926            .add_group(
1927                "0".into(),
1928                Some(vec!["0".into(), "1".into()]),
1929                Some(vec![0, 1]),
1930            )
1931            .unwrap();
1932        graphrecord
1933            .add_group(
1934                "1".into(),
1935                Some(vec!["0".into(), "1".into()]),
1936                Some(vec![0, 1]),
1937            )
1938            .unwrap();
1939
1940        let inferred_schema = Schema::new_inferred(HashMap::default(), GroupSchema::default());
1941
1942        assert!(graphrecord.set_schema(inferred_schema).is_ok());
1943
1944        assert_eq!(schema, *graphrecord.get_schema());
1945    }
1946
1947    #[test]
1948    fn test_invalid_set_schema() {
1949        let mut graphrecord = GraphRecord::new();
1950
1951        graphrecord
1952            .add_node("0".into(), HashMap::from([("attribute2".into(), 1.into())]))
1953            .unwrap();
1954
1955        let schema = Schema::new_provided(
1956            HashMap::default(),
1957            GroupSchema::new(
1958                AttributeSchema::from([("attribute".into(), DataType::Int.into())]),
1959                AttributeSchema::from([("attribute".into(), DataType::Int.into())]),
1960            ),
1961        );
1962
1963        let previous_schema = graphrecord.get_schema().clone();
1964
1965        assert!(
1966            graphrecord
1967                .set_schema(schema.clone())
1968                .is_err_and(|e| { matches!(e, GraphRecordError::SchemaError(_)) })
1969        );
1970
1971        assert_eq!(previous_schema, *graphrecord.get_schema());
1972
1973        let mut graphrecord = GraphRecord::new();
1974
1975        graphrecord
1976            .add_node("0".into(), HashMap::from([("attribute".into(), 1.into())]))
1977            .unwrap();
1978        graphrecord
1979            .add_node("1".into(), HashMap::from([("attribute".into(), 1.into())]))
1980            .unwrap();
1981        graphrecord
1982            .add_edge(
1983                "0".into(),
1984                "1".into(),
1985                HashMap::from([("attribute2".into(), 1.into())]),
1986            )
1987            .unwrap();
1988
1989        let previous_schema = graphrecord.get_schema().clone();
1990
1991        assert!(
1992            graphrecord
1993                .set_schema(schema)
1994                .is_err_and(|e| { matches!(e, GraphRecordError::SchemaError(_)) })
1995        );
1996
1997        assert_eq!(previous_schema, *graphrecord.get_schema());
1998    }
1999
2000    #[test]
2001    fn test_freeze_schema() {
2002        let mut graphrecord = GraphRecord::new();
2003
2004        assert_eq!(
2005            SchemaType::Inferred,
2006            *graphrecord.get_schema().schema_type()
2007        );
2008
2009        graphrecord.freeze_schema().unwrap();
2010
2011        assert_eq!(
2012            SchemaType::Provided,
2013            *graphrecord.get_schema().schema_type()
2014        );
2015    }
2016
2017    #[test]
2018    fn test_unfreeze_schema() {
2019        let schema = Schema::new_provided(HashMap::default(), GroupSchema::default());
2020        let mut graphrecord = GraphRecord::with_schema(schema);
2021
2022        assert_eq!(
2023            *graphrecord.get_schema().schema_type(),
2024            SchemaType::Provided
2025        );
2026
2027        graphrecord.unfreeze_schema().unwrap();
2028
2029        assert_eq!(
2030            *graphrecord.get_schema().schema_type(),
2031            SchemaType::Inferred
2032        );
2033    }
2034
2035    #[test]
2036    fn test_node_indices() {
2037        let graphrecord = create_graphrecord();
2038
2039        let node_indices: Vec<_> = create_nodes()
2040            .into_iter()
2041            .map(|(node_index, _)| node_index)
2042            .collect();
2043
2044        for node_index in graphrecord.node_indices() {
2045            assert!(node_indices.contains(node_index));
2046        }
2047    }
2048
2049    #[test]
2050    fn test_node_attributes() {
2051        let graphrecord = create_graphrecord();
2052
2053        let attributes = graphrecord.node_attributes(&"0".into()).unwrap();
2054
2055        assert_eq!(&create_nodes()[0].1, attributes);
2056    }
2057
2058    #[test]
2059    fn test_invalid_node_attributes() {
2060        let graphrecord = create_graphrecord();
2061
2062        // Querying a non-existing node should fail
2063        assert!(
2064            graphrecord
2065                .node_attributes(&"50".into())
2066                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2067        );
2068    }
2069
2070    #[test]
2071    fn test_node_attributes_mut() {
2072        let mut graphrecord = create_graphrecord();
2073
2074        let node_index = "0".into();
2075        let mut attributes = graphrecord.node_attributes_mut(&node_index).unwrap();
2076
2077        let new_attributes = HashMap::from([("0".into(), "1".into()), ("2".into(), "3".into())]);
2078
2079        attributes
2080            .replace_attributes(new_attributes.clone())
2081            .unwrap();
2082
2083        assert_eq!(
2084            &new_attributes,
2085            graphrecord.node_attributes(&node_index).unwrap()
2086        );
2087    }
2088
2089    #[test]
2090    fn test_invalid_node_attributes_mut() {
2091        let mut graphrecord = create_graphrecord();
2092
2093        // Accessing the node attributes of a non-existing node should fail
2094        assert!(
2095            graphrecord
2096                .node_attributes_mut(&"50".into())
2097                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2098        );
2099    }
2100
2101    #[test]
2102    fn test_outgoing_edges() {
2103        let graphrecord = create_graphrecord();
2104
2105        let edges = graphrecord.outgoing_edges(&"0".into()).unwrap();
2106
2107        assert_eq!(2, edges.count());
2108    }
2109
2110    #[test]
2111    fn test_invalid_outgoing_edges() {
2112        let graphrecord = create_graphrecord();
2113
2114        assert!(
2115            graphrecord
2116                .outgoing_edges(&"50".into())
2117                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2118        );
2119    }
2120
2121    #[test]
2122    fn test_incoming_edges() {
2123        let graphrecord = create_graphrecord();
2124
2125        let edges = graphrecord.incoming_edges(&"2".into()).unwrap();
2126
2127        assert_eq!(2, edges.count());
2128    }
2129
2130    #[test]
2131    fn test_invalid_incoming_edges() {
2132        let graphrecord = create_graphrecord();
2133
2134        assert!(
2135            graphrecord
2136                .incoming_edges(&"50".into())
2137                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2138        );
2139    }
2140
2141    #[test]
2142    fn test_edge_indices() {
2143        let graphrecord = create_graphrecord();
2144        let edges = [0, 1, 2, 3];
2145
2146        for edge in graphrecord.edge_indices() {
2147            assert!(edges.contains(edge));
2148        }
2149    }
2150
2151    #[test]
2152    fn test_edge_attributes() {
2153        let graphrecord = create_graphrecord();
2154
2155        let attributes = graphrecord.edge_attributes(&0).unwrap();
2156
2157        assert_eq!(&create_edges()[0].2, attributes);
2158    }
2159
2160    #[test]
2161    fn test_invalid_edge_attributes() {
2162        let graphrecord = create_graphrecord();
2163
2164        // Querying a non-existing node should fail
2165        assert!(
2166            graphrecord
2167                .edge_attributes(&50)
2168                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2169        );
2170    }
2171
2172    #[test]
2173    fn test_edge_attributes_mut() {
2174        let mut graphrecord = create_graphrecord();
2175
2176        let mut attributes = graphrecord.edge_attributes_mut(&0).unwrap();
2177
2178        let new_attributes = HashMap::from([("0".into(), "1".into()), ("2".into(), "3".into())]);
2179
2180        attributes
2181            .replace_attributes(new_attributes.clone())
2182            .unwrap();
2183
2184        assert_eq!(&new_attributes, graphrecord.edge_attributes(&0).unwrap());
2185    }
2186
2187    #[test]
2188    fn test_invalid_edge_attributes_mut() {
2189        let mut graphrecord = create_graphrecord();
2190
2191        // Accessing the edge attributes of a non-existing edge should fail
2192        assert!(
2193            graphrecord
2194                .edge_attributes_mut(&50)
2195                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2196        );
2197    }
2198
2199    #[test]
2200    fn test_edge_endpoints() {
2201        let graphrecord = create_graphrecord();
2202
2203        let edge = &create_edges()[0];
2204
2205        let endpoints = graphrecord.edge_endpoints(&0).unwrap();
2206
2207        assert_eq!(&edge.0, endpoints.0);
2208
2209        assert_eq!(&edge.1, endpoints.1);
2210    }
2211
2212    #[test]
2213    fn test_invalid_edge_endpoints() {
2214        let graphrecord = create_graphrecord();
2215
2216        // Accessing the edge endpoints of a non-existing edge should fail
2217        assert!(
2218            graphrecord
2219                .edge_endpoints(&50)
2220                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2221        );
2222    }
2223
2224    #[test]
2225    fn test_edges_connecting() {
2226        let graphrecord = create_graphrecord();
2227
2228        let first_index = "0".into();
2229        let second_index = "1".into();
2230        let edges_connecting =
2231            graphrecord.edges_connecting(vec![&first_index], vec![&second_index]);
2232
2233        assert_eq!(vec![&0], edges_connecting.collect::<Vec<_>>());
2234
2235        let first_index = "0".into();
2236        let second_index = "3".into();
2237        let edges_connecting =
2238            graphrecord.edges_connecting(vec![&first_index], vec![&second_index]);
2239
2240        assert_eq!(0, edges_connecting.count());
2241
2242        let first_index = "0".into();
2243        let second_index = "1".into();
2244        let third_index = "2".into();
2245        let mut edges_connecting: Vec<_> = graphrecord
2246            .edges_connecting(vec![&first_index, &second_index], vec![&third_index])
2247            .collect();
2248
2249        edges_connecting.sort();
2250        assert_eq!(vec![&2, &3], edges_connecting);
2251
2252        let first_index = "0".into();
2253        let second_index = "1".into();
2254        let third_index = "2".into();
2255        let fourth_index = "3".into();
2256        let mut edges_connecting: Vec<_> = graphrecord
2257            .edges_connecting(
2258                vec![&first_index, &second_index],
2259                vec![&third_index, &fourth_index],
2260            )
2261            .collect();
2262
2263        edges_connecting.sort();
2264        assert_eq!(vec![&2, &3], edges_connecting);
2265    }
2266
2267    #[test]
2268    fn test_edges_connecting_undirected() {
2269        let graphrecord = create_graphrecord();
2270
2271        let first_index = "0".into();
2272        let second_index = "1".into();
2273        let mut edges_connecting: Vec<_> = graphrecord
2274            .edges_connecting_undirected(vec![&first_index], vec![&second_index])
2275            .collect();
2276
2277        edges_connecting.sort();
2278        assert_eq!(vec![&0, &1], edges_connecting);
2279    }
2280
2281    #[test]
2282    fn test_add_node() {
2283        let mut graphrecord = GraphRecord::new();
2284
2285        assert_eq!(0, graphrecord.node_count());
2286
2287        graphrecord.add_node("0".into(), HashMap::new()).unwrap();
2288
2289        assert_eq!(1, graphrecord.node_count());
2290
2291        graphrecord.freeze_schema().unwrap();
2292
2293        graphrecord.add_node("1".into(), HashMap::new()).unwrap();
2294
2295        assert_eq!(2, graphrecord.node_count());
2296    }
2297
2298    #[test]
2299    fn test_invalid_add_node() {
2300        let mut graphrecord = create_graphrecord();
2301
2302        assert!(
2303            graphrecord
2304                .add_node("0".into(), HashMap::new())
2305                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
2306        );
2307
2308        graphrecord.freeze_schema().unwrap();
2309
2310        assert!(
2311            graphrecord
2312                .add_node("4".into(), HashMap::from([("attribute".into(), 1.into())]))
2313                .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2314        );
2315    }
2316
2317    #[test]
2318    fn test_remove_node() {
2319        let mut graphrecord = create_graphrecord();
2320
2321        graphrecord
2322            .add_group("group".into(), Some(vec!["0".into()]), Some(vec![0]))
2323            .unwrap();
2324
2325        let nodes = create_nodes();
2326
2327        assert_eq!(4, graphrecord.node_count());
2328        assert_eq!(4, graphrecord.edge_count());
2329        assert_eq!(
2330            1,
2331            graphrecord
2332                .nodes_in_group(&("group".into()))
2333                .unwrap()
2334                .count()
2335        );
2336        assert_eq!(
2337            1,
2338            graphrecord
2339                .edges_in_group(&("group".into()))
2340                .unwrap()
2341                .count()
2342        );
2343
2344        assert_eq!(nodes[0].1, graphrecord.remove_node(&"0".into()).unwrap());
2345
2346        assert_eq!(3, graphrecord.node_count());
2347        assert_eq!(1, graphrecord.edge_count());
2348        assert_eq!(
2349            0,
2350            graphrecord
2351                .nodes_in_group(&("group".into()))
2352                .unwrap()
2353                .count()
2354        );
2355        assert_eq!(
2356            0,
2357            graphrecord
2358                .edges_in_group(&("group".into()))
2359                .unwrap()
2360                .count()
2361        );
2362
2363        let mut graphrecord = GraphRecord::new();
2364
2365        graphrecord.add_node(0.into(), HashMap::new()).unwrap();
2366        graphrecord
2367            .add_edge(0.into(), 0.into(), HashMap::new())
2368            .unwrap();
2369
2370        assert_eq!(1, graphrecord.node_count());
2371        assert_eq!(1, graphrecord.edge_count());
2372
2373        assert!(graphrecord.remove_node(&0.into()).is_ok());
2374
2375        assert_eq!(0, graphrecord.node_count());
2376        assert_eq!(0, graphrecord.edge_count());
2377    }
2378
2379    #[test]
2380    fn test_invalid_remove_node() {
2381        let mut graphrecord = create_graphrecord();
2382
2383        // Removing a non-existing node should fail
2384        assert!(
2385            graphrecord
2386                .remove_node(&"50".into())
2387                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2388        );
2389    }
2390
2391    #[test]
2392    fn test_add_nodes() {
2393        let mut graphrecord = GraphRecord::new();
2394
2395        assert_eq!(0, graphrecord.node_count());
2396
2397        let nodes = create_nodes();
2398
2399        graphrecord.add_nodes(nodes).unwrap();
2400
2401        assert_eq!(4, graphrecord.node_count());
2402    }
2403
2404    #[test]
2405    fn test_invalid_add_nodes() {
2406        let mut graphrecord = create_graphrecord();
2407
2408        let nodes = create_nodes();
2409
2410        assert!(
2411            graphrecord
2412                .add_nodes(nodes)
2413                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
2414        );
2415    }
2416
2417    #[test]
2418    fn test_add_nodes_dataframe() {
2419        let mut graphrecord = GraphRecord::new();
2420
2421        assert_eq!(0, graphrecord.node_count());
2422
2423        let nodes_dataframe = create_nodes_dataframe().unwrap();
2424
2425        graphrecord
2426            .add_nodes_dataframes(vec![(nodes_dataframe, "index".to_string())])
2427            .unwrap();
2428
2429        assert_eq!(2, graphrecord.node_count());
2430    }
2431
2432    #[test]
2433    fn test_add_edge() {
2434        let mut graphrecord = create_graphrecord();
2435
2436        assert_eq!(4, graphrecord.edge_count());
2437
2438        graphrecord
2439            .add_edge("0".into(), "3".into(), HashMap::new())
2440            .unwrap();
2441
2442        assert_eq!(5, graphrecord.edge_count());
2443
2444        graphrecord.freeze_schema().unwrap();
2445
2446        graphrecord
2447            .add_edge("0".into(), "3".into(), HashMap::new())
2448            .unwrap();
2449
2450        assert_eq!(6, graphrecord.edge_count());
2451    }
2452
2453    #[test]
2454    fn test_invalid_add_edge() {
2455        let mut graphrecord = GraphRecord::new();
2456
2457        let nodes = create_nodes();
2458
2459        graphrecord.add_nodes(nodes).unwrap();
2460
2461        // Adding an edge pointing to a non-existing node should fail
2462        assert!(
2463            graphrecord
2464                .add_edge("0".into(), "50".into(), HashMap::new())
2465                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2466        );
2467
2468        // Adding an edge from a non-existing node should fail
2469        assert!(
2470            graphrecord
2471                .add_edge("50".into(), "0".into(), HashMap::new())
2472                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2473        );
2474
2475        graphrecord.freeze_schema().unwrap();
2476
2477        assert!(
2478            graphrecord
2479                .add_edge(
2480                    "0".into(),
2481                    "3".into(),
2482                    HashMap::from([("attribute".into(), 1.into())])
2483                )
2484                .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2485        );
2486    }
2487
2488    #[test]
2489    fn test_remove_edge() {
2490        let mut graphrecord = create_graphrecord();
2491
2492        let edges = create_edges();
2493
2494        assert_eq!(edges[0].2, graphrecord.remove_edge(&0).unwrap());
2495    }
2496
2497    #[test]
2498    fn test_invalid_remove_edge() {
2499        let mut graphrecord = create_graphrecord();
2500
2501        // Removing a non-existing edge should fail
2502        assert!(
2503            graphrecord
2504                .remove_edge(&50)
2505                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2506        );
2507    }
2508
2509    #[test]
2510    fn test_add_edges() {
2511        let mut graphrecord = GraphRecord::new();
2512
2513        let nodes = create_nodes();
2514
2515        graphrecord.add_nodes(nodes).unwrap();
2516
2517        assert_eq!(0, graphrecord.edge_count());
2518
2519        let edges = create_edges();
2520
2521        graphrecord.add_edges(edges).unwrap();
2522
2523        assert_eq!(4, graphrecord.edge_count());
2524    }
2525
2526    #[test]
2527    fn test_add_edges_dataframe() {
2528        let mut graphrecord = GraphRecord::new();
2529
2530        let nodes = create_nodes();
2531
2532        graphrecord.add_nodes(nodes).unwrap();
2533
2534        assert_eq!(0, graphrecord.edge_count());
2535
2536        let edges = create_edges_dataframe().unwrap();
2537
2538        graphrecord
2539            .add_edges_dataframes(vec![(edges, "from", "to")])
2540            .unwrap();
2541
2542        assert_eq!(2, graphrecord.edge_count());
2543    }
2544
2545    #[test]
2546    fn test_add_group() {
2547        let mut graphrecord = create_graphrecord();
2548
2549        assert_eq!(0, graphrecord.group_count());
2550
2551        graphrecord.add_group("0".into(), None, None).unwrap();
2552
2553        assert_eq!(1, graphrecord.group_count());
2554
2555        graphrecord
2556            .add_group("1".into(), Some(vec!["0".into(), "1".into()]), None)
2557            .unwrap();
2558
2559        assert_eq!(2, graphrecord.group_count());
2560
2561        assert_eq!(2, graphrecord.nodes_in_group(&"1".into()).unwrap().count());
2562    }
2563
2564    #[test]
2565    fn test_invalid_add_group() {
2566        let mut graphrecord = create_graphrecord();
2567
2568        // Adding a group with a non-existing node should fail
2569        assert!(
2570            graphrecord
2571                .add_group("0".into(), Some(vec!["50".into()]), None)
2572                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2573        );
2574
2575        // Adding a group with a non-existing edge should fail
2576        assert!(
2577            graphrecord
2578                .add_group("0".into(), None, Some(vec![50]))
2579                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2580        );
2581
2582        graphrecord.add_group("0".into(), None, None).unwrap();
2583
2584        // Adding an already existing group should fail
2585        assert!(
2586            graphrecord
2587                .add_group("0".into(), None, None)
2588                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
2589        );
2590
2591        graphrecord.freeze_schema().unwrap();
2592
2593        assert!(
2594            graphrecord
2595                .add_group("2".into(), None, None)
2596                .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2597        );
2598
2599        graphrecord.remove_group(&"0".into()).unwrap();
2600
2601        assert!(
2602            graphrecord
2603                .add_group("0".into(), Some(vec!["0".into()]), None)
2604                .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2605        );
2606        assert!(
2607            graphrecord
2608                .add_group("0".into(), None, Some(vec![0]))
2609                .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2610        );
2611    }
2612
2613    #[test]
2614    fn test_remove_group() {
2615        let mut graphrecord = create_graphrecord();
2616
2617        graphrecord.add_group("0".into(), None, None).unwrap();
2618
2619        assert_eq!(1, graphrecord.group_count());
2620
2621        graphrecord.remove_group(&"0".into()).unwrap();
2622
2623        assert_eq!(0, graphrecord.group_count());
2624    }
2625
2626    #[test]
2627    fn test_invalid_remove_group() {
2628        let mut graphrecord = GraphRecord::new();
2629
2630        // Removing a non-existing group should fail
2631        assert!(
2632            graphrecord
2633                .remove_group(&"0".into())
2634                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2635        );
2636    }
2637
2638    #[test]
2639    fn test_add_node_to_group() {
2640        let mut graphrecord = create_graphrecord();
2641
2642        graphrecord
2643            .add_group("0".into(), Some(vec!["0".into(), "1".into()]), None)
2644            .unwrap();
2645
2646        assert_eq!(2, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
2647
2648        graphrecord
2649            .add_node_to_group("0".into(), "2".into())
2650            .unwrap();
2651
2652        assert_eq!(3, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
2653
2654        graphrecord
2655            .add_node("4".into(), HashMap::from([("test".into(), "test".into())]))
2656            .unwrap();
2657
2658        graphrecord
2659            .add_group("1".into(), Some(vec!["4".into()]), None)
2660            .unwrap();
2661
2662        graphrecord.freeze_schema().unwrap();
2663
2664        graphrecord
2665            .add_node("5".into(), HashMap::from([("test".into(), "test".into())]))
2666            .unwrap();
2667
2668        assert!(
2669            graphrecord
2670                .add_node_to_group("1".into(), "5".into())
2671                .is_ok()
2672        );
2673
2674        assert_eq!(2, graphrecord.nodes_in_group(&"1".into()).unwrap().count());
2675    }
2676
2677    #[test]
2678    fn test_invalid_add_node_to_group() {
2679        let mut graphrecord = create_graphrecord();
2680
2681        graphrecord
2682            .add_group("0".into(), Some(vec!["0".into()]), None)
2683            .unwrap();
2684
2685        // Adding a non-existing node to a group should fail
2686        assert!(
2687            graphrecord
2688                .add_node_to_group("0".into(), "50".into())
2689                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2690        );
2691
2692        // Adding a node to a group that already is in the group should fail
2693        assert!(
2694            graphrecord
2695                .add_node_to_group("0".into(), "0".into())
2696                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
2697        );
2698
2699        let mut graphrecord = GraphRecord::new();
2700
2701        graphrecord
2702            .add_node("0".into(), HashMap::from([("test".into(), "test".into())]))
2703            .unwrap();
2704        graphrecord.add_group("group".into(), None, None).unwrap();
2705
2706        graphrecord.freeze_schema().unwrap();
2707
2708        assert!(
2709            graphrecord
2710                .add_node_to_group("group".into(), "0".into())
2711                .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2712        );
2713    }
2714
2715    #[test]
2716    fn test_add_node_to_groups() {
2717        let mut graphrecord = create_graphrecord();
2718
2719        graphrecord
2720            .add_group("0".into(), Some(vec!["0".into()]), None)
2721            .unwrap();
2722        graphrecord
2723            .add_group("1".into(), Some(vec!["1".into()]), None)
2724            .unwrap();
2725
2726        graphrecord
2727            .add_node_to_groups(&["0".into(), "1".into()], "2".into())
2728            .unwrap();
2729
2730        assert_eq!(2, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
2731        assert_eq!(2, graphrecord.nodes_in_group(&"1".into()).unwrap().count());
2732    }
2733
2734    #[test]
2735    fn test_invalid_add_node_to_groups() {
2736        let mut graphrecord = create_graphrecord();
2737
2738        graphrecord
2739            .add_group("0".into(), Some(vec!["0".into()]), None)
2740            .unwrap();
2741        graphrecord
2742            .add_group("1".into(), Some(vec!["1".into()]), None)
2743            .unwrap();
2744
2745        assert!(
2746            graphrecord
2747                .add_node_to_groups(&["0".into(), "1".into()], "50".into())
2748                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2749        );
2750
2751        assert!(
2752            graphrecord
2753                .add_node_to_groups(&["0".into(), "1".into()], "0".into())
2754                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
2755        );
2756
2757        let mut graphrecord = GraphRecord::new();
2758
2759        graphrecord
2760            .add_node("0".into(), HashMap::from([("test".into(), "test".into())]))
2761            .unwrap();
2762        graphrecord.add_group("group".into(), None, None).unwrap();
2763        graphrecord.add_group("group2".into(), None, None).unwrap();
2764
2765        graphrecord.freeze_schema().unwrap();
2766
2767        assert!(
2768            graphrecord
2769                .add_node_to_groups(&["group".into(), "group2".into()], "0".into())
2770                .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2771        );
2772    }
2773
2774    #[test]
2775    fn test_add_edge_to_group() {
2776        let mut graphrecord = create_graphrecord();
2777
2778        graphrecord
2779            .add_group("0".into(), None, Some(vec![0, 1]))
2780            .unwrap();
2781
2782        assert_eq!(2, graphrecord.edges_in_group(&"0".into()).unwrap().count());
2783
2784        graphrecord.add_edge_to_group("0".into(), 2).unwrap();
2785
2786        assert_eq!(3, graphrecord.edges_in_group(&"0".into()).unwrap().count());
2787
2788        graphrecord
2789            .add_edge("0".into(), "1".into(), HashMap::new())
2790            .unwrap();
2791
2792        graphrecord
2793            .add_group("1".into(), None, Some(vec![3]))
2794            .unwrap();
2795
2796        graphrecord.freeze_schema().unwrap();
2797
2798        let edge_index = graphrecord
2799            .add_edge("0".into(), "1".into(), HashMap::new())
2800            .unwrap();
2801
2802        assert!(
2803            graphrecord
2804                .add_edge_to_group("1".into(), edge_index)
2805                .is_ok()
2806        );
2807
2808        assert_eq!(2, graphrecord.edges_in_group(&"1".into()).unwrap().count());
2809    }
2810
2811    #[test]
2812    fn test_invalid_add_edge_to_group() {
2813        let mut graphrecord = create_graphrecord();
2814
2815        graphrecord
2816            .add_group("0".into(), None, Some(vec![0]))
2817            .unwrap();
2818
2819        // Adding a non-existing edge to a group should fail
2820        assert!(
2821            graphrecord
2822                .add_edge_to_group("0".into(), 50)
2823                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2824        );
2825
2826        // Adding an edge to a group that already is in the group should fail
2827        assert!(
2828            graphrecord
2829                .add_edge_to_group("0".into(), 0)
2830                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
2831        );
2832
2833        let mut graphrecord = GraphRecord::new();
2834
2835        graphrecord.add_node("0".into(), HashMap::new()).unwrap();
2836        graphrecord
2837            .add_edge(
2838                "0".into(),
2839                "0".into(),
2840                HashMap::from([("test".into(), "test".into())]),
2841            )
2842            .unwrap();
2843        graphrecord.add_group("group".into(), None, None).unwrap();
2844
2845        graphrecord.freeze_schema().unwrap();
2846
2847        assert!(
2848            graphrecord
2849                .add_edge_to_group("group".into(), 0)
2850                .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2851        );
2852    }
2853
2854    #[test]
2855    fn test_add_edge_to_groups() {
2856        let mut graphrecord = create_graphrecord();
2857
2858        graphrecord
2859            .add_group("0".into(), None, Some(vec![0]))
2860            .unwrap();
2861        graphrecord
2862            .add_group("1".into(), None, Some(vec![1]))
2863            .unwrap();
2864
2865        graphrecord
2866            .add_edge_to_groups(&["0".into(), "1".into()], 2)
2867            .unwrap();
2868
2869        assert_eq!(2, graphrecord.edges_in_group(&"0".into()).unwrap().count());
2870        assert_eq!(2, graphrecord.edges_in_group(&"1".into()).unwrap().count());
2871    }
2872
2873    #[test]
2874    fn test_invalid_add_edge_to_groups() {
2875        let mut graphrecord = create_graphrecord();
2876
2877        graphrecord
2878            .add_group("0".into(), None, Some(vec![0]))
2879            .unwrap();
2880        graphrecord
2881            .add_group("1".into(), None, Some(vec![1]))
2882            .unwrap();
2883
2884        assert!(
2885            graphrecord
2886                .add_edge_to_groups(&["0".into(), "1".into()], 50)
2887                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2888        );
2889
2890        assert!(
2891            graphrecord
2892                .add_edge_to_groups(&["0".into(), "1".into()], 0)
2893                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
2894        );
2895
2896        let mut graphrecord = GraphRecord::new();
2897
2898        graphrecord.add_node("0".into(), HashMap::new()).unwrap();
2899        graphrecord
2900            .add_edge(
2901                "0".into(),
2902                "0".into(),
2903                HashMap::from([("test".into(), "test".into())]),
2904            )
2905            .unwrap();
2906        graphrecord.add_group("group".into(), None, None).unwrap();
2907        graphrecord.add_group("group2".into(), None, None).unwrap();
2908
2909        graphrecord.freeze_schema().unwrap();
2910
2911        assert!(
2912            graphrecord
2913                .add_edge_to_groups(&["group".into(), "group2".into()], 0)
2914                .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
2915        );
2916    }
2917
2918    #[test]
2919    fn test_remove_node_from_group() {
2920        let mut graphrecord = create_graphrecord();
2921
2922        graphrecord
2923            .add_group("0".into(), Some(vec!["0".into(), "1".into()]), None)
2924            .unwrap();
2925
2926        assert_eq!(2, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
2927
2928        graphrecord
2929            .remove_node_from_group(&"0".into(), &"0".into())
2930            .unwrap();
2931
2932        assert_eq!(1, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
2933    }
2934
2935    #[test]
2936    fn test_invalid_remove_node_from_group() {
2937        let mut graphrecord = create_graphrecord();
2938
2939        graphrecord
2940            .add_group("0".into(), Some(vec!["0".into()]), None)
2941            .unwrap();
2942
2943        // Removing a node from a non-existing group should fail
2944        assert!(
2945            graphrecord
2946                .remove_node_from_group(&"50".into(), &"0".into())
2947                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2948        );
2949
2950        // Removing a non-existing node from a group should fail
2951        assert!(
2952            graphrecord
2953                .remove_node_from_group(&"0".into(), &"50".into())
2954                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2955        );
2956
2957        // Removing a node from a group it is not in should fail
2958        assert!(
2959            graphrecord
2960                .remove_node_from_group(&"0".into(), &"1".into())
2961                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
2962        );
2963    }
2964
2965    #[test]
2966    fn test_remove_node_from_groups() {
2967        let mut graphrecord = create_graphrecord();
2968
2969        graphrecord
2970            .add_group("0".into(), Some(vec!["0".into(), "1".into()]), None)
2971            .unwrap();
2972        graphrecord
2973            .add_group("1".into(), Some(vec!["0".into(), "2".into()]), None)
2974            .unwrap();
2975
2976        graphrecord
2977            .remove_node_from_groups(&["0".into(), "1".into()], &"0".into())
2978            .unwrap();
2979
2980        assert_eq!(1, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
2981        assert_eq!(1, graphrecord.nodes_in_group(&"1".into()).unwrap().count());
2982    }
2983
2984    #[test]
2985    fn test_invalid_remove_node_from_groups() {
2986        let mut graphrecord = create_graphrecord();
2987
2988        graphrecord
2989            .add_group("0".into(), Some(vec!["0".into()]), None)
2990            .unwrap();
2991        graphrecord
2992            .add_group("1".into(), Some(vec!["1".into()]), None)
2993            .unwrap();
2994
2995        assert!(
2996            graphrecord
2997                .remove_node_from_groups(&["0".into(), "1".into()], &"50".into())
2998                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
2999        );
3000
3001        assert!(
3002            graphrecord
3003                .remove_node_from_groups(&["0".into(), "1".into()], &"1".into())
3004                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
3005        );
3006    }
3007
3008    #[test]
3009    fn test_remove_edge_from_group() {
3010        let mut graphrecord = create_graphrecord();
3011
3012        graphrecord
3013            .add_group("0".into(), None, Some(vec![0, 1]))
3014            .unwrap();
3015
3016        assert_eq!(2, graphrecord.edges_in_group(&"0".into()).unwrap().count());
3017
3018        graphrecord.remove_edge_from_group(&"0".into(), &0).unwrap();
3019
3020        assert_eq!(1, graphrecord.edges_in_group(&"0".into()).unwrap().count());
3021    }
3022
3023    #[test]
3024    fn test_invalid_remove_edge_from_group() {
3025        let mut graphrecord = create_graphrecord();
3026
3027        graphrecord
3028            .add_group("0".into(), None, Some(vec![0]))
3029            .unwrap();
3030
3031        // Removing an edge from a non-existing group should fail
3032        assert!(
3033            graphrecord
3034                .remove_edge_from_group(&"50".into(), &0)
3035                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3036        );
3037
3038        // Removing a non-existing edge from a group should fail
3039        assert!(
3040            graphrecord
3041                .remove_edge_from_group(&"0".into(), &50)
3042                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3043        );
3044
3045        // Removing an edge from a group it is not in should fail
3046        assert!(
3047            graphrecord
3048                .remove_edge_from_group(&"0".into(), &1)
3049                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
3050        );
3051    }
3052
3053    #[test]
3054    fn test_remove_edge_from_groups() {
3055        let mut graphrecord = create_graphrecord();
3056
3057        graphrecord
3058            .add_group("0".into(), None, Some(vec![0, 1]))
3059            .unwrap();
3060        graphrecord
3061            .add_group("1".into(), None, Some(vec![0, 2]))
3062            .unwrap();
3063
3064        graphrecord
3065            .remove_edge_from_groups(&["0".into(), "1".into()], &0)
3066            .unwrap();
3067
3068        assert_eq!(1, graphrecord.edges_in_group(&"0".into()).unwrap().count());
3069        assert_eq!(1, graphrecord.edges_in_group(&"1".into()).unwrap().count());
3070    }
3071
3072    #[test]
3073    fn test_invalid_remove_edge_from_groups() {
3074        let mut graphrecord = create_graphrecord();
3075
3076        graphrecord
3077            .add_group("0".into(), None, Some(vec![0]))
3078            .unwrap();
3079        graphrecord
3080            .add_group("1".into(), None, Some(vec![1]))
3081            .unwrap();
3082
3083        assert!(
3084            graphrecord
3085                .remove_edge_from_groups(&["0".into(), "1".into()], &50)
3086                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3087        );
3088
3089        assert!(
3090            graphrecord
3091                .remove_edge_from_groups(&["0".into(), "1".into()], &1)
3092                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
3093        );
3094    }
3095
3096    #[test]
3097    fn test_add_nodes_to_groups() {
3098        let mut graphrecord = create_graphrecord();
3099
3100        graphrecord
3101            .add_group("0".into(), Some(vec!["0".into()]), None)
3102            .unwrap();
3103        graphrecord
3104            .add_group("1".into(), Some(vec!["1".into()]), None)
3105            .unwrap();
3106
3107        graphrecord
3108            .add_nodes_to_groups(&["0".into(), "1".into()], vec!["2".into(), "3".into()])
3109            .unwrap();
3110
3111        assert_eq!(3, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
3112        assert_eq!(3, graphrecord.nodes_in_group(&"1".into()).unwrap().count());
3113    }
3114
3115    #[test]
3116    fn test_invalid_add_nodes_to_groups() {
3117        let mut graphrecord = create_graphrecord();
3118
3119        graphrecord
3120            .add_group("0".into(), Some(vec!["0".into()]), None)
3121            .unwrap();
3122        graphrecord
3123            .add_group("1".into(), Some(vec!["1".into()]), None)
3124            .unwrap();
3125
3126        assert!(
3127            graphrecord
3128                .add_nodes_to_groups(&["0".into(), "1".into()], vec!["50".into()],)
3129                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3130        );
3131
3132        assert!(
3133            graphrecord
3134                .add_nodes_to_groups(&["0".into(), "1".into()], vec!["0".into()],)
3135                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
3136        );
3137
3138        let mut graphrecord = GraphRecord::new();
3139
3140        graphrecord
3141            .add_node("0".into(), HashMap::from([("test".into(), "test".into())]))
3142            .unwrap();
3143        graphrecord.add_group("group".into(), None, None).unwrap();
3144        graphrecord.add_group("group2".into(), None, None).unwrap();
3145
3146        graphrecord.freeze_schema().unwrap();
3147
3148        assert!(
3149            graphrecord
3150                .add_nodes_to_groups(&["group".into(), "group2".into()], vec!["0".into()],)
3151                .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
3152        );
3153    }
3154
3155    #[test]
3156    fn test_add_edges_to_groups() {
3157        let mut graphrecord = create_graphrecord();
3158
3159        graphrecord
3160            .add_group("0".into(), None, Some(vec![0]))
3161            .unwrap();
3162        graphrecord
3163            .add_group("1".into(), None, Some(vec![1]))
3164            .unwrap();
3165
3166        graphrecord
3167            .add_edges_to_groups(&["0".into(), "1".into()], vec![2, 3])
3168            .unwrap();
3169
3170        assert_eq!(3, graphrecord.edges_in_group(&"0".into()).unwrap().count());
3171        assert_eq!(3, graphrecord.edges_in_group(&"1".into()).unwrap().count());
3172    }
3173
3174    #[test]
3175    fn test_invalid_add_edges_to_groups() {
3176        let mut graphrecord = create_graphrecord();
3177
3178        graphrecord
3179            .add_group("0".into(), None, Some(vec![0]))
3180            .unwrap();
3181        graphrecord
3182            .add_group("1".into(), None, Some(vec![1]))
3183            .unwrap();
3184
3185        assert!(
3186            graphrecord
3187                .add_edges_to_groups(&["0".into(), "1".into()], vec![50])
3188                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3189        );
3190
3191        assert!(
3192            graphrecord
3193                .add_edges_to_groups(&["0".into(), "1".into()], vec![0])
3194                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
3195        );
3196
3197        let mut graphrecord = GraphRecord::new();
3198
3199        graphrecord.add_node("0".into(), HashMap::new()).unwrap();
3200        graphrecord
3201            .add_edge(
3202                "0".into(),
3203                "0".into(),
3204                HashMap::from([("test".into(), "test".into())]),
3205            )
3206            .unwrap();
3207        graphrecord.add_group("group".into(), None, None).unwrap();
3208        graphrecord.add_group("group2".into(), None, None).unwrap();
3209
3210        graphrecord.freeze_schema().unwrap();
3211
3212        assert!(
3213            graphrecord
3214                .add_edges_to_groups(&["group".into(), "group2".into()], vec![0])
3215                .is_err_and(|e| matches!(e, GraphRecordError::SchemaError(_)))
3216        );
3217    }
3218
3219    #[test]
3220    fn test_remove_nodes_from_groups() {
3221        let mut graphrecord = create_graphrecord();
3222
3223        graphrecord
3224            .add_group(
3225                "0".into(),
3226                Some(vec!["0".into(), "1".into(), "2".into()]),
3227                None,
3228            )
3229            .unwrap();
3230        graphrecord
3231            .add_group(
3232                "1".into(),
3233                Some(vec!["0".into(), "1".into(), "2".into()]),
3234                None,
3235            )
3236            .unwrap();
3237
3238        graphrecord
3239            .remove_nodes_from_groups(&["0".into(), "1".into()], &["0".into(), "1".into()])
3240            .unwrap();
3241
3242        assert_eq!(1, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
3243        assert_eq!(1, graphrecord.nodes_in_group(&"1".into()).unwrap().count());
3244    }
3245
3246    #[test]
3247    fn test_invalid_remove_nodes_from_groups() {
3248        let mut graphrecord = create_graphrecord();
3249
3250        graphrecord
3251            .add_group("0".into(), Some(vec!["0".into()]), None)
3252            .unwrap();
3253        graphrecord
3254            .add_group("1".into(), Some(vec!["1".into()]), None)
3255            .unwrap();
3256
3257        assert!(
3258            graphrecord
3259                .remove_nodes_from_groups(&["0".into(), "1".into()], &["50".into()],)
3260                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3261        );
3262
3263        assert!(
3264            graphrecord
3265                .remove_nodes_from_groups(&["0".into(), "1".into()], &["1".into()],)
3266                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
3267        );
3268    }
3269
3270    #[test]
3271    fn test_remove_edges_from_groups() {
3272        let mut graphrecord = create_graphrecord();
3273
3274        graphrecord
3275            .add_group("0".into(), None, Some(vec![0, 1, 2]))
3276            .unwrap();
3277        graphrecord
3278            .add_group("1".into(), None, Some(vec![0, 1, 2]))
3279            .unwrap();
3280
3281        graphrecord
3282            .remove_edges_from_groups(&["0".into(), "1".into()], &[0, 1])
3283            .unwrap();
3284
3285        assert_eq!(1, graphrecord.edges_in_group(&"0".into()).unwrap().count());
3286        assert_eq!(1, graphrecord.edges_in_group(&"1".into()).unwrap().count());
3287    }
3288
3289    #[test]
3290    fn test_invalid_remove_edges_from_groups() {
3291        let mut graphrecord = create_graphrecord();
3292
3293        graphrecord
3294            .add_group("0".into(), None, Some(vec![0]))
3295            .unwrap();
3296        graphrecord
3297            .add_group("1".into(), None, Some(vec![1]))
3298            .unwrap();
3299
3300        assert!(
3301            graphrecord
3302                .remove_edges_from_groups(&["0".into(), "1".into()], &[50])
3303                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3304        );
3305
3306        assert!(
3307            graphrecord
3308                .remove_edges_from_groups(&["0".into(), "1".into()], &[1])
3309                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
3310        );
3311    }
3312
3313    #[test]
3314    fn test_add_node_with_groups() {
3315        let mut graphrecord = create_graphrecord();
3316
3317        graphrecord.add_group("0".into(), None, None).unwrap();
3318        graphrecord.add_group("1".into(), None, None).unwrap();
3319
3320        graphrecord
3321            .add_node_with_groups(
3322                "4".into(),
3323                HashMap::from([("lorem".into(), "ipsum".into())]),
3324                &["0".into(), "1".into()],
3325            )
3326            .unwrap();
3327
3328        assert_eq!(5, graphrecord.node_count());
3329        assert_eq!(1, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
3330        assert_eq!(1, graphrecord.nodes_in_group(&"1".into()).unwrap().count());
3331    }
3332
3333    #[test]
3334    fn test_invalid_add_node_with_groups() {
3335        let mut graphrecord = create_graphrecord();
3336
3337        graphrecord.add_group("0".into(), None, None).unwrap();
3338        graphrecord.add_group("1".into(), None, None).unwrap();
3339
3340        assert!(
3341            graphrecord
3342                .add_node_with_groups("0".into(), HashMap::new(), &["0".into(), "1".into()],)
3343                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
3344        );
3345    }
3346
3347    #[test]
3348    fn test_add_edge_with_groups() {
3349        let mut graphrecord = create_graphrecord();
3350
3351        graphrecord.add_group("0".into(), None, None).unwrap();
3352        graphrecord.add_group("1".into(), None, None).unwrap();
3353
3354        let edge_index = graphrecord
3355            .add_edge_with_groups(
3356                "0".into(),
3357                "1".into(),
3358                HashMap::from([("sed".into(), "do".into())]),
3359                &["0".into(), "1".into()],
3360            )
3361            .unwrap();
3362
3363        assert_eq!(5, graphrecord.edge_count());
3364        assert_eq!(4, edge_index);
3365        assert_eq!(1, graphrecord.edges_in_group(&"0".into()).unwrap().count());
3366        assert_eq!(1, graphrecord.edges_in_group(&"1".into()).unwrap().count());
3367    }
3368
3369    #[test]
3370    fn test_invalid_add_edge_with_groups() {
3371        let mut graphrecord = create_graphrecord();
3372
3373        graphrecord.add_group("0".into(), None, None).unwrap();
3374        graphrecord.add_group("1".into(), None, None).unwrap();
3375
3376        assert!(
3377            graphrecord
3378                .add_edge_with_groups(
3379                    "50".into(),
3380                    "0".into(),
3381                    HashMap::new(),
3382                    &["0".into(), "1".into()],
3383                )
3384                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3385        );
3386
3387        assert!(
3388            graphrecord
3389                .add_edge_with_groups(
3390                    "0".into(),
3391                    "50".into(),
3392                    HashMap::new(),
3393                    &["0".into(), "1".into()],
3394                )
3395                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3396        );
3397    }
3398
3399    #[test]
3400    fn test_add_nodes_with_groups() {
3401        let mut graphrecord = GraphRecord::new();
3402
3403        graphrecord.add_group("0".into(), None, None).unwrap();
3404        graphrecord.add_group("1".into(), None, None).unwrap();
3405
3406        graphrecord
3407            .add_nodes_with_groups(
3408                vec![
3409                    (
3410                        "0".into(),
3411                        HashMap::from([("lorem".into(), "ipsum".into())]),
3412                    ),
3413                    (
3414                        "1".into(),
3415                        HashMap::from([("amet".into(), "consectetur".into())]),
3416                    ),
3417                ],
3418                &["0".into(), "1".into()],
3419            )
3420            .unwrap();
3421
3422        assert_eq!(2, graphrecord.node_count());
3423        assert_eq!(2, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
3424        assert_eq!(2, graphrecord.nodes_in_group(&"1".into()).unwrap().count());
3425    }
3426
3427    #[test]
3428    fn test_invalid_add_nodes_with_groups() {
3429        let mut graphrecord = create_graphrecord();
3430
3431        graphrecord.add_group("0".into(), None, None).unwrap();
3432        graphrecord.add_group("1".into(), None, None).unwrap();
3433
3434        assert!(
3435            graphrecord
3436                .add_nodes_with_groups(
3437                    vec![("0".into(), HashMap::new())],
3438                    &["0".into(), "1".into()],
3439                )
3440                .is_err_and(|e| matches!(e, GraphRecordError::AssertionError(_)))
3441        );
3442    }
3443
3444    #[test]
3445    fn test_add_edges_with_groups() {
3446        let mut graphrecord = create_graphrecord();
3447
3448        graphrecord.add_group("0".into(), None, None).unwrap();
3449        graphrecord.add_group("1".into(), None, None).unwrap();
3450
3451        let edge_indices = graphrecord
3452            .add_edges_with_groups(
3453                vec![
3454                    (
3455                        "0".into(),
3456                        "1".into(),
3457                        HashMap::from([("sed".into(), "do".into())]),
3458                    ),
3459                    (
3460                        "1".into(),
3461                        "0".into(),
3462                        HashMap::from([("sed".into(), "do".into())]),
3463                    ),
3464                ],
3465                &["0".into(), "1".into()],
3466            )
3467            .unwrap();
3468
3469        assert_eq!(6, graphrecord.edge_count());
3470        assert_eq!(vec![4, 5], edge_indices);
3471        assert_eq!(2, graphrecord.edges_in_group(&"0".into()).unwrap().count());
3472        assert_eq!(2, graphrecord.edges_in_group(&"1".into()).unwrap().count());
3473    }
3474
3475    #[test]
3476    fn test_invalid_add_edges_with_groups() {
3477        let mut graphrecord = create_graphrecord();
3478
3479        graphrecord.add_group("0".into(), None, None).unwrap();
3480        graphrecord.add_group("1".into(), None, None).unwrap();
3481
3482        assert!(
3483            graphrecord
3484                .add_edges_with_groups(
3485                    vec![("50".into(), "0".into(), HashMap::new())],
3486                    &["0".into(), "1".into()],
3487                )
3488                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3489        );
3490
3491        assert!(
3492            graphrecord
3493                .add_edges_with_groups(
3494                    vec![("0".into(), "50".into(), HashMap::new())],
3495                    &["0".into(), "1".into()],
3496                )
3497                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3498        );
3499    }
3500
3501    #[test]
3502    fn test_add_nodes_dataframes_with_groups() {
3503        let mut graphrecord = GraphRecord::new();
3504
3505        graphrecord.add_group("0".into(), None, None).unwrap();
3506        graphrecord.add_group("1".into(), None, None).unwrap();
3507
3508        let nodes_dataframe = create_nodes_dataframe().unwrap();
3509
3510        graphrecord
3511            .add_nodes_dataframes_with_groups(
3512                vec![NodeDataFrameInput {
3513                    dataframe: nodes_dataframe,
3514                    index_column: "index".to_string(),
3515                }],
3516                &["0".into(), "1".into()],
3517            )
3518            .unwrap();
3519
3520        assert_eq!(2, graphrecord.node_count());
3521        assert_eq!(2, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
3522        assert_eq!(2, graphrecord.nodes_in_group(&"1".into()).unwrap().count());
3523    }
3524
3525    #[test]
3526    fn test_add_edges_dataframes_with_groups() {
3527        let mut graphrecord = GraphRecord::new();
3528
3529        let nodes = create_nodes();
3530
3531        graphrecord.add_nodes(nodes).unwrap();
3532
3533        graphrecord.add_group("0".into(), None, None).unwrap();
3534        graphrecord.add_group("1".into(), None, None).unwrap();
3535
3536        let edges_dataframe = create_edges_dataframe().unwrap();
3537
3538        let edge_indices = graphrecord
3539            .add_edges_dataframes_with_groups(
3540                vec![EdgeDataFrameInput {
3541                    dataframe: edges_dataframe,
3542                    source_index_column: "from".to_string(),
3543                    target_index_column: "to".to_string(),
3544                }],
3545                &["0".into(), "1".into()],
3546            )
3547            .unwrap();
3548
3549        assert_eq!(2, graphrecord.edge_count());
3550        assert_eq!(2, edge_indices.len());
3551        assert_eq!(2, graphrecord.edges_in_group(&"0".into()).unwrap().count());
3552        assert_eq!(2, graphrecord.edges_in_group(&"1".into()).unwrap().count());
3553    }
3554
3555    #[test]
3556    fn test_groups() {
3557        let mut graphrecord = create_graphrecord();
3558
3559        graphrecord.add_group("0".into(), None, None).unwrap();
3560
3561        let groups: Vec<_> = graphrecord.groups().collect();
3562
3563        assert_eq!(vec![&(GraphRecordAttribute::from("0"))], groups);
3564    }
3565
3566    #[test]
3567    fn test_nodes_in_group() {
3568        let mut graphrecord = create_graphrecord();
3569
3570        graphrecord.add_group("0".into(), None, None).unwrap();
3571
3572        assert_eq!(0, graphrecord.nodes_in_group(&"0".into()).unwrap().count());
3573
3574        graphrecord
3575            .add_group("1".into(), Some(vec!["0".into()]), None)
3576            .unwrap();
3577
3578        assert_eq!(1, graphrecord.nodes_in_group(&"1".into()).unwrap().count());
3579    }
3580
3581    #[test]
3582    fn test_invalid_nodes_in_group() {
3583        let graphrecord = create_graphrecord();
3584
3585        // Querying a non-existing group should fail
3586        assert!(
3587            graphrecord
3588                .nodes_in_group(&"0".into())
3589                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3590        );
3591    }
3592
3593    #[test]
3594    fn test_edges_in_group() {
3595        let mut graphrecord = create_graphrecord();
3596
3597        graphrecord.add_group("0".into(), None, None).unwrap();
3598
3599        assert_eq!(0, graphrecord.edges_in_group(&"0".into()).unwrap().count());
3600
3601        graphrecord
3602            .add_group("1".into(), None, Some(vec![0]))
3603            .unwrap();
3604
3605        assert_eq!(1, graphrecord.edges_in_group(&"1".into()).unwrap().count());
3606    }
3607
3608    #[test]
3609    fn test_invalid_edges_in_group() {
3610        let graphrecord = create_graphrecord();
3611
3612        // Querying a non-existing group should fail
3613        assert!(
3614            graphrecord
3615                .edges_in_group(&"0".into())
3616                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3617        );
3618    }
3619
3620    #[test]
3621    fn test_groups_of_node() {
3622        let mut graphrecord = create_graphrecord();
3623
3624        graphrecord
3625            .add_group("0".into(), Some(vec!["0".into()]), None)
3626            .unwrap();
3627
3628        assert_eq!(1, graphrecord.groups_of_node(&"0".into()).unwrap().count());
3629    }
3630
3631    #[test]
3632    fn test_invalid_groups_of_node() {
3633        let graphrecord = create_graphrecord();
3634
3635        // Queyring the groups of a non-existing node should fail
3636        assert!(
3637            graphrecord
3638                .groups_of_node(&"50".into())
3639                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3640        );
3641    }
3642
3643    #[test]
3644    fn test_groups_of_edge() {
3645        let mut graphrecord = create_graphrecord();
3646
3647        graphrecord
3648            .add_group("0".into(), None, Some(vec![0]))
3649            .unwrap();
3650
3651        assert_eq!(1, graphrecord.groups_of_edge(&0).unwrap().count());
3652    }
3653
3654    #[test]
3655    fn test_invalid_groups_of_edge() {
3656        let graphrecord = create_graphrecord();
3657
3658        // Queyring the groups of a non-existing edge should fail
3659        assert!(
3660            graphrecord
3661                .groups_of_edge(&50)
3662                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3663        );
3664    }
3665
3666    #[test]
3667    fn test_node_count() {
3668        let mut graphrecord = GraphRecord::new();
3669
3670        assert_eq!(0, graphrecord.node_count());
3671
3672        graphrecord.add_node("0".into(), HashMap::new()).unwrap();
3673
3674        assert_eq!(1, graphrecord.node_count());
3675    }
3676
3677    #[test]
3678    fn test_edge_count() {
3679        let mut graphrecord = GraphRecord::new();
3680
3681        graphrecord.add_node("0".into(), HashMap::new()).unwrap();
3682        graphrecord.add_node("1".into(), HashMap::new()).unwrap();
3683
3684        assert_eq!(0, graphrecord.edge_count());
3685
3686        graphrecord
3687            .add_edge("0".into(), "1".into(), HashMap::new())
3688            .unwrap();
3689
3690        assert_eq!(1, graphrecord.edge_count());
3691    }
3692
3693    #[test]
3694    fn test_group_count() {
3695        let mut graphrecord = create_graphrecord();
3696
3697        assert_eq!(0, graphrecord.group_count());
3698
3699        graphrecord.add_group("0".into(), None, None).unwrap();
3700
3701        assert_eq!(1, graphrecord.group_count());
3702    }
3703
3704    #[test]
3705    fn test_contains_node() {
3706        let graphrecord = create_graphrecord();
3707
3708        assert!(graphrecord.contains_node(&"0".into()));
3709
3710        assert!(!graphrecord.contains_node(&"50".into()));
3711    }
3712
3713    #[test]
3714    fn test_contains_edge() {
3715        let graphrecord = create_graphrecord();
3716
3717        assert!(graphrecord.contains_edge(&0));
3718
3719        assert!(!graphrecord.contains_edge(&50));
3720    }
3721
3722    #[test]
3723    fn test_contains_group() {
3724        let mut graphrecord = create_graphrecord();
3725
3726        assert!(!graphrecord.contains_group(&"0".into()));
3727
3728        graphrecord.add_group("0".into(), None, None).unwrap();
3729
3730        assert!(graphrecord.contains_group(&"0".into()));
3731    }
3732
3733    #[test]
3734    fn test_neighbors() {
3735        let graphrecord = create_graphrecord();
3736
3737        let neighbors = graphrecord.neighbors_outgoing(&"0".into()).unwrap();
3738
3739        assert_eq!(2, neighbors.count());
3740    }
3741
3742    #[test]
3743    fn test_invalid_neighbors() {
3744        let graphrecord = GraphRecord::new();
3745
3746        // Querying neighbors of a non-existing node sohuld fail
3747        assert!(
3748            graphrecord
3749                .neighbors_outgoing(&"0".into())
3750                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3751        );
3752    }
3753
3754    #[test]
3755    fn test_neighbors_undirected() {
3756        let graphrecord = create_graphrecord();
3757
3758        let neighbors = graphrecord.neighbors_outgoing(&"2".into()).unwrap();
3759        assert_eq!(0, neighbors.count());
3760
3761        let neighbors = graphrecord.neighbors_undirected(&"2".into()).unwrap();
3762        assert_eq!(2, neighbors.count());
3763    }
3764
3765    #[test]
3766    fn test_invalid_neighbors_undirected() {
3767        let graphrecord = create_graphrecord();
3768
3769        assert!(
3770            graphrecord
3771                .neighbors_undirected(&"50".into())
3772                .is_err_and(|e| matches!(e, GraphRecordError::IndexError(_)))
3773        );
3774    }
3775
3776    #[test]
3777    fn test_clear() {
3778        let mut graphrecord = create_graphrecord();
3779
3780        graphrecord.clear().unwrap();
3781
3782        assert_eq!(0, graphrecord.node_count());
3783        assert_eq!(0, graphrecord.edge_count());
3784        assert_eq!(0, graphrecord.group_count());
3785    }
3786}