Skip to main content

scirs2_graph/io/
dot.rs

1//! DOT format I/O for graphs (Graphviz format)
2//!
3//! This module provides functionality for reading and writing graphs in DOT format,
4//! which is the standard format used by Graphviz for graph visualization.
5//!
6//! # Format Specification
7//!
8//! DOT format supports both directed and undirected graphs:
9//! - Undirected graphs use `graph` keyword and `--` for edges
10//! - Directed graphs use `digraph` keyword and `->` for edges
11//!
12//! # Examples
13//!
14//! ## Undirected graph:
15//! ```text
16//! graph G {
17//!     1 -- 2 [weight=1.5];
18//!     2 -- 3 [weight=2.0];
19//! }
20//! ```
21//!
22//! ## Directed graph:
23//! ```text
24//! digraph G {
25//!     1 -> 2 [weight=1.5];
26//!     2 -> 3 [weight=2.0];
27//! }
28//! ```
29//!
30//! # Usage
31//!
32//! ```rust
33//! use std::fs::File;
34//! use std::io::Write;
35//! use tempfile::NamedTempFile;
36//! use scirs2_graph::base::Graph;
37//! use scirs2_graph::io::dot::{read_dot_format, write_dot_format};
38//!
39//! // Create a temporary file with DOT data
40//! let mut temp_file = NamedTempFile::new().expect("Test operation failed");
41//! writeln!(temp_file, "graph G {{").expect("Test operation failed");
42//! writeln!(temp_file, "    1 -- 2;").expect("Test operation failed");
43//! writeln!(temp_file, "    2 -- 3;").expect("Test operation failed");
44//! writeln!(temp_file, "}}").expect("Test operation failed");
45//! temp_file.flush().expect("Test operation failed");
46//!
47//! // Read the graph
48//! let graph: Graph<i32, f64> = read_dot_format(temp_file.path(), false).expect("Test operation failed");
49//! assert_eq!(graph.node_count(), 3);
50//! assert_eq!(graph.edge_count(), 2);
51//! ```
52
53use std::fs::File;
54use std::io::{BufRead, BufReader, Write};
55use std::path::Path;
56use std::str::FromStr;
57
58use crate::base::{DiGraph, EdgeWeight, Graph, Node};
59use crate::error::{GraphError, Result};
60
61/// DOT format parser state
62#[derive(Debug, Clone, PartialEq, Eq)]
63enum ParseState {
64    /// Looking for graph declaration
65    Header,
66    /// Inside graph body
67    Body,
68    /// Finished parsing
69    Done,
70}
71
72/// DOT graph type
73#[derive(Debug, Clone, PartialEq, Eq)]
74enum GraphType {
75    /// Undirected graph (graph keyword)
76    Undirected,
77    /// Directed graph (digraph keyword)
78    Directed,
79}
80
81/// Read an undirected graph from DOT format
82///
83/// # Arguments
84///
85/// * `path` - Path to the input file
86/// * `weighted` - Whether to parse edge weights from attributes
87///
88/// # Returns
89///
90/// * `Ok(Graph)` - The graph read from the file
91/// * `Err(GraphError)` - If there was an error reading or parsing the file
92///
93/// # Format
94///
95/// The DOT format supports:
96/// - Node declarations: `node_id;` or `node_id [attributes];`
97/// - Edge declarations: `node1 -- node2;` or `node1 -- node2 [attributes];`
98/// - Comments: `// comment` or `/* comment */`
99/// - Attributes in square brackets: `[weight=1.5, label="edge"]`
100#[allow(dead_code)]
101pub fn read_dot_format<N, E, P>(path: P, weighted: bool) -> Result<Graph<N, E>>
102where
103    N: Node + std::fmt::Debug + FromStr + Clone,
104    E: EdgeWeight + std::marker::Copy + std::fmt::Debug + std::default::Default + FromStr,
105    P: AsRef<Path>,
106{
107    let file = File::open(path)?;
108    let reader = BufReader::new(file);
109    let mut graph = Graph::new();
110    let mut state = ParseState::Header;
111    let mut graph_type = None;
112    let mut in_multiline_comment = false;
113
114    for (line_num, line_result) in reader.lines().enumerate() {
115        let line = line_result?;
116        let (processed_line, comment_continues) = remove_comments(&line, in_multiline_comment);
117        in_multiline_comment = comment_continues;
118        let line = processed_line.trim().to_string();
119
120        if line.is_empty() {
121            continue;
122        }
123
124        match state {
125            ParseState::Header => {
126                if let Some(detected_type) = parse_header(&line)? {
127                    graph_type = Some(detected_type);
128                    state = ParseState::Body;
129                }
130            }
131            ParseState::Body => {
132                if line.contains('}') {
133                    state = ParseState::Done;
134                    break;
135                }
136
137                // Parse edge or node declarations
138                if let Some(graph_type) = &graph_type {
139                    parse_graph_element(&line, graph_type, &mut graph, weighted, line_num + 1)?;
140                }
141            }
142            ParseState::Done => break,
143        }
144    }
145
146    // Validate we found a proper graph declaration
147    if graph_type.is_none() {
148        return Err(GraphError::Other(
149            "No valid graph declaration found".to_string(),
150        ));
151    }
152
153    if state != ParseState::Done && state != ParseState::Body {
154        return Err(GraphError::Other(
155            "Incomplete DOT file - missing closing brace".to_string(),
156        ));
157    }
158
159    Ok(graph)
160}
161
162/// Read a directed graph from DOT format
163///
164/// # Arguments
165///
166/// * `path` - Path to the input file
167/// * `weighted` - Whether to parse edge weights from attributes
168///
169/// # Returns
170///
171/// * `Ok(DiGraph)` - The directed graph read from the file
172/// * `Err(GraphError)` - If there was an error reading or parsing the file
173#[allow(dead_code)]
174pub fn read_dot_format_digraph<N, E, P>(path: P, weighted: bool) -> Result<DiGraph<N, E>>
175where
176    N: Node + std::fmt::Debug + FromStr + Clone,
177    E: EdgeWeight + std::marker::Copy + std::fmt::Debug + std::default::Default + FromStr,
178    P: AsRef<Path>,
179{
180    let file = File::open(path)?;
181    let reader = BufReader::new(file);
182    let mut graph = DiGraph::new();
183    let mut state = ParseState::Header;
184    let mut graph_type = None;
185    let mut in_multiline_comment = false;
186
187    for (line_num, line_result) in reader.lines().enumerate() {
188        let line = line_result?;
189        let (processed_line, comment_continues) = remove_comments(&line, in_multiline_comment);
190        in_multiline_comment = comment_continues;
191        let line = processed_line.trim().to_string();
192
193        if line.is_empty() {
194            continue;
195        }
196
197        match state {
198            ParseState::Header => {
199                if let Some(detected_type) = parse_header(&line)? {
200                    graph_type = Some(detected_type);
201                    state = ParseState::Body;
202                }
203            }
204            ParseState::Body => {
205                if line.contains('}') {
206                    state = ParseState::Done;
207                    break;
208                }
209
210                // Parse edge or node declarations
211                if let Some(graph_type) = &graph_type {
212                    parse_digraph_element(&line, graph_type, &mut graph, weighted, line_num + 1)?;
213                }
214            }
215            ParseState::Done => break,
216        }
217    }
218
219    // Validate we found a proper graph declaration
220    if graph_type.is_none() {
221        return Err(GraphError::Other(
222            "No valid graph declaration found".to_string(),
223        ));
224    }
225
226    if state != ParseState::Done && state != ParseState::Body {
227        return Err(GraphError::Other(
228            "Incomplete DOT file - missing closing brace".to_string(),
229        ));
230    }
231
232    Ok(graph)
233}
234
235/// Write an undirected graph to DOT format
236///
237/// # Arguments
238///
239/// * `graph` - The graph to write
240/// * `path` - Path to the output file
241/// * `weighted` - Whether to include edge weights in attributes
242///
243/// # Returns
244///
245/// * `Ok(())` - If the graph was written successfully
246/// * `Err(GraphError)` - If there was an error writing the file
247#[allow(dead_code)]
248pub fn write_dot_format<N, E, Ix, P>(graph: &Graph<N, E, Ix>, path: P, weighted: bool) -> Result<()>
249where
250    N: Node + std::fmt::Debug + std::fmt::Display + Clone,
251    E: EdgeWeight
252        + std::marker::Copy
253        + std::fmt::Debug
254        + std::default::Default
255        + std::fmt::Display
256        + Clone,
257    Ix: petgraph::graph::IndexType,
258    P: AsRef<Path>,
259{
260    let mut file = File::create(path)?;
261
262    writeln!(file, "graph G {{")?;
263    writeln!(file, "    // Generated by scirs2-graph")?;
264
265    // Write nodes
266    for node in graph.nodes() {
267        writeln!(file, "    {node};")?;
268    }
269
270    writeln!(file)?;
271
272    // Write edges
273    for edge in graph.edges() {
274        if weighted {
275            writeln!(
276                file,
277                "    {} -- {} [weight={}];",
278                edge.source, edge.target, edge.weight
279            )?;
280        } else {
281            writeln!(file, "    {} -- {};", edge.source, edge.target)?;
282        }
283    }
284
285    writeln!(file, "}}")?;
286
287    Ok(())
288}
289
290/// Write a directed graph to DOT format
291///
292/// # Arguments
293///
294/// * `graph` - The directed graph to write
295/// * `path` - Path to the output file
296/// * `weighted` - Whether to include edge weights in attributes
297///
298/// # Returns
299///
300/// * `Ok(())` - If the graph was written successfully
301/// * `Err(GraphError)` - If there was an error writing the file
302#[allow(dead_code)]
303pub fn write_dot_format_digraph<N, E, Ix, P>(
304    graph: &DiGraph<N, E, Ix>,
305    path: P,
306    weighted: bool,
307) -> Result<()>
308where
309    N: Node + std::fmt::Debug + std::fmt::Display + Clone,
310    E: EdgeWeight
311        + std::marker::Copy
312        + std::fmt::Debug
313        + std::default::Default
314        + std::fmt::Display
315        + Clone,
316    Ix: petgraph::graph::IndexType,
317    P: AsRef<Path>,
318{
319    let mut file = File::create(path)?;
320
321    writeln!(file, "digraph G {{")?;
322    writeln!(file, "    // Generated by scirs2-graph")?;
323
324    // Write nodes
325    for node in graph.nodes() {
326        writeln!(file, "    {node};")?;
327    }
328
329    writeln!(file)?;
330
331    // Write edges
332    for edge in graph.edges() {
333        if weighted {
334            writeln!(
335                file,
336                "    {} -> {} [weight={}];",
337                edge.source, edge.target, edge.weight
338            )?;
339        } else {
340            writeln!(file, "    {} -> {};", edge.source, edge.target)?;
341        }
342    }
343
344    writeln!(file, "}}")?;
345
346    Ok(())
347}
348
349// Helper functions
350
351/// Remove comments from a line, handling multi-line /* */ comments
352/// Returns (processed_line, is_still_in_multiline_comment)
353#[allow(dead_code)]
354fn remove_comments(line: &str, in_multiline_comment: bool) -> (String, bool) {
355    let mut result = String::new();
356    let mut chars = line.chars().peekable();
357    let mut in_comment = in_multiline_comment;
358
359    while let Some(ch) = chars.next() {
360        if in_comment {
361            // We're inside a multi-line comment, look for */
362            if ch == '*' && chars.peek() == Some(&'/') {
363                chars.next(); // consume '/'
364                in_comment = false;
365            }
366            // Skip characters inside comment
367        } else {
368            // Not in a comment, check for comment start
369            if ch == '/' {
370                if let Some(&next_ch) = chars.peek() {
371                    if next_ch == '/' {
372                        // Single-line comment, skip rest of line
373                        break;
374                    } else if next_ch == '*' {
375                        // Multi-line comment start
376                        chars.next(); // consume '*'
377                        in_comment = true;
378                        continue;
379                    }
380                }
381            }
382            if !in_comment {
383                result.push(ch);
384            }
385        }
386    }
387
388    (result, in_comment)
389}
390
391/// Parse the header line to determine graph type
392#[allow(dead_code)]
393fn parse_header(line: &str) -> Result<Option<GraphType>> {
394    let line = line.trim();
395
396    if line.starts_with("graph") && line.contains('{') {
397        return Ok(Some(GraphType::Undirected));
398    }
399
400    if line.starts_with("digraph") && line.contains('{') {
401        return Ok(Some(GraphType::Directed));
402    }
403
404    // Skip other lines until we find a graph declaration
405    Ok(None)
406}
407
408/// Parse a graph element (node or edge) for undirected graphs
409#[allow(dead_code)]
410fn parse_graph_element<N, E>(
411    line: &str,
412    graph_type: &GraphType,
413    graph: &mut Graph<N, E>,
414    weighted: bool,
415    line_num: usize,
416) -> Result<()>
417where
418    N: Node + std::fmt::Debug + FromStr + Clone,
419    E: EdgeWeight + std::marker::Copy + std::fmt::Debug + std::default::Default + FromStr,
420{
421    let line = line.trim_end_matches(';').trim();
422
423    // Check for edge based on graph type
424    let edge_separator = match graph_type {
425        GraphType::Undirected => "--",
426        GraphType::Directed => "->",
427    };
428
429    if line.contains(edge_separator) {
430        parse_edge(line, edge_separator, graph, weighted, line_num)?;
431    } else if !line.is_empty() && !line.starts_with('}') && !line.contains('[') {
432        // Parse as node declaration (simple node without attributes)
433        parse_node(line, line_num)?;
434    }
435
436    Ok(())
437}
438
439/// Parse a graph element (node or edge) for directed graphs
440#[allow(dead_code)]
441fn parse_digraph_element<N, E>(
442    line: &str,
443    graph_type: &GraphType,
444    graph: &mut DiGraph<N, E>,
445    weighted: bool,
446    line_num: usize,
447) -> Result<()>
448where
449    N: Node + std::fmt::Debug + FromStr + Clone,
450    E: EdgeWeight + std::marker::Copy + std::fmt::Debug + std::default::Default + FromStr,
451{
452    let line = line.trim_end_matches(';').trim();
453
454    // Check for edge based on graph type
455    let edge_separator = match graph_type {
456        GraphType::Undirected => "--",
457        GraphType::Directed => "->",
458    };
459
460    if line.contains(edge_separator) {
461        parse_digraph_edge(line, edge_separator, graph, weighted, line_num)?;
462    } else if !line.is_empty() && !line.starts_with('}') && !line.contains('[') {
463        // Parse as node declaration (simple node without attributes)
464        parse_node(line, line_num)?;
465    }
466
467    Ok(())
468}
469
470/// Parse an edge declaration for undirected graphs
471#[allow(dead_code)]
472fn parse_edge<N, E>(
473    line: &str,
474    edge_separator: &str,
475    graph: &mut Graph<N, E>,
476    weighted: bool,
477    line_num: usize,
478) -> Result<()>
479where
480    N: Node + std::fmt::Debug + FromStr + Clone,
481    E: EdgeWeight + std::marker::Copy + std::fmt::Debug + std::default::Default + FromStr,
482{
483    // Split by edge separator
484    let parts: Vec<&str> = line.split(edge_separator).collect();
485    if parts.len() != 2 {
486        return Err(GraphError::Other(format!(
487            "Invalid edge format on line {line_num}: {line}"
488        )));
489    }
490
491    let source_part = parts[0].trim();
492    let target_part = parts[1].trim();
493
494    // Parse source node
495    let source_node = N::from_str(source_part).map_err(|_| {
496        GraphError::Other(format!(
497            "Failed to parse source node '{source_part}' on line {line_num}"
498        ))
499    })?;
500
501    // Parse target node and attributes
502    let (target_str, attributes) = if target_part.contains('[') {
503        let bracket_pos = target_part.find('[').expect("Test operation failed");
504        (
505            target_part[..bracket_pos].trim(),
506            Some(&target_part[bracket_pos..]),
507        )
508    } else {
509        (target_part, None)
510    };
511
512    let target_node = N::from_str(target_str).map_err(|_| {
513        GraphError::Other(format!(
514            "Failed to parse target node '{target_str}' on line {line_num}"
515        ))
516    })?;
517
518    // Parse weight from attributes if needed
519    let weight = if let (true, Some(attrs)) = (weighted, attributes) {
520        parse_weight_from_attributes(attrs)?
521    } else {
522        E::default()
523    };
524
525    // Add edge
526    graph.add_edge(source_node, target_node, weight)?;
527
528    Ok(())
529}
530
531/// Parse an edge declaration for directed graphs
532#[allow(dead_code)]
533fn parse_digraph_edge<N, E>(
534    line: &str,
535    edge_separator: &str,
536    graph: &mut DiGraph<N, E>,
537    weighted: bool,
538    line_num: usize,
539) -> Result<()>
540where
541    N: Node + std::fmt::Debug + FromStr + Clone,
542    E: EdgeWeight + std::marker::Copy + std::fmt::Debug + std::default::Default + FromStr,
543{
544    // Split by edge separator
545    let parts: Vec<&str> = line.split(edge_separator).collect();
546    if parts.len() != 2 {
547        return Err(GraphError::Other(format!(
548            "Invalid edge format on line {line_num}: {line}"
549        )));
550    }
551
552    let source_part = parts[0].trim();
553    let target_part = parts[1].trim();
554
555    // Parse source node
556    let source_node = N::from_str(source_part).map_err(|_| {
557        GraphError::Other(format!(
558            "Failed to parse source node '{source_part}' on line {line_num}"
559        ))
560    })?;
561
562    // Parse target node and attributes
563    let (target_str, attributes) = if target_part.contains('[') {
564        let bracket_pos = target_part.find('[').expect("Test operation failed");
565        (
566            target_part[..bracket_pos].trim(),
567            Some(&target_part[bracket_pos..]),
568        )
569    } else {
570        (target_part, None)
571    };
572
573    let target_node = N::from_str(target_str).map_err(|_| {
574        GraphError::Other(format!(
575            "Failed to parse target node '{target_str}' on line {line_num}"
576        ))
577    })?;
578
579    // Parse weight from attributes if needed
580    let weight = if let (true, Some(attrs)) = (weighted, attributes) {
581        parse_weight_from_attributes(attrs)?
582    } else {
583        E::default()
584    };
585
586    // Add edge
587    graph.add_edge(source_node, target_node, weight)?;
588
589    Ok(())
590}
591
592/// Parse a node declaration (currently just validates the syntax)
593#[allow(dead_code)]
594fn parse_node(_line: &str, _line_num: usize) -> Result<()> {
595    // For now, we don't need to explicitly add nodes since they'll be added
596    // when edges are added. This function validates node syntax.
597    Ok(())
598}
599
600/// Parse weight from DOT attributes
601#[allow(dead_code)]
602fn parse_weight_from_attributes<E>(attributes: &str) -> Result<E>
603where
604    E: EdgeWeight + std::marker::Copy + std::fmt::Debug + std::default::Default + FromStr,
605{
606    // Look for weight=value pattern
607    if let Some(weight_start) = attributes.find("weight=") {
608        let weight_part = &attributes[weight_start + 7..]; // Skip "weight="
609
610        // Find end of weight value (space, comma, or closing bracket)
611        let weight_end = weight_part
612            .find(&[' ', ',', ']'][..])
613            .unwrap_or(weight_part.len());
614
615        let weight_str = &weight_part[..weight_end];
616
617        return E::from_str(weight_str)
618            .map_err(|_| GraphError::Other(format!("Failed to parse weight: {weight_str}")));
619    }
620
621    Ok(E::default())
622}
623
624#[cfg(test)]
625mod tests {
626    use super::*;
627    use std::io::Write;
628    use tempfile::NamedTempFile;
629
630    #[test]
631    fn test_read_undirected_dot() {
632        let mut temp_file = NamedTempFile::new().expect("Test operation failed");
633        writeln!(temp_file, "graph G {{").expect("Test operation failed");
634        writeln!(temp_file, "    1 -- 2;").expect("Test operation failed");
635        writeln!(temp_file, "    2 -- 3;").expect("Test operation failed");
636        writeln!(temp_file, "}}").expect("Test operation failed");
637        temp_file.flush().expect("Test operation failed");
638
639        let graph: Graph<i32, f64> =
640            read_dot_format(temp_file.path(), false).expect("Test operation failed");
641
642        assert_eq!(graph.node_count(), 3);
643        assert_eq!(graph.edge_count(), 2);
644    }
645
646    #[test]
647    fn test_read_directed_dot() {
648        let mut temp_file = NamedTempFile::new().expect("Test operation failed");
649        writeln!(temp_file, "digraph G {{").expect("Test operation failed");
650        writeln!(temp_file, "    1 -> 2;").expect("Test operation failed");
651        writeln!(temp_file, "    2 -> 3;").expect("Test operation failed");
652        writeln!(temp_file, "}}").expect("Test operation failed");
653        temp_file.flush().expect("Test operation failed");
654
655        let graph: DiGraph<i32, f64> =
656            read_dot_format_digraph(temp_file.path(), false).expect("Test operation failed");
657
658        assert_eq!(graph.node_count(), 3);
659        assert_eq!(graph.edge_count(), 2);
660    }
661
662    #[test]
663    fn test_read_weighted_dot() {
664        let mut temp_file = NamedTempFile::new().expect("Test operation failed");
665        writeln!(temp_file, "graph G {{").expect("Test operation failed");
666        writeln!(temp_file, "    1 -- 2 [weight=1.5];").expect("Test operation failed");
667        writeln!(temp_file, "    2 -- 3 [weight=2.0];").expect("Test operation failed");
668        writeln!(temp_file, "}}").expect("Test operation failed");
669        temp_file.flush().expect("Test operation failed");
670
671        let graph: Graph<i32, f64> =
672            read_dot_format(temp_file.path(), true).expect("Test operation failed");
673
674        assert_eq!(graph.node_count(), 3);
675        assert_eq!(graph.edge_count(), 2);
676    }
677
678    #[test]
679    fn test_write_read_roundtrip() {
680        let mut original_graph: Graph<i32, f64> = Graph::new();
681        original_graph
682            .add_edge(1i32, 2i32, 1.5f64)
683            .expect("Test operation failed");
684        original_graph
685            .add_edge(2i32, 3i32, 2.0f64)
686            .expect("Test operation failed");
687
688        let temp_file = NamedTempFile::new().expect("Test operation failed");
689        write_dot_format(&original_graph, temp_file.path(), true).expect("Test operation failed");
690
691        let read_graph: Graph<i32, f64> =
692            read_dot_format(temp_file.path(), true).expect("Test operation failed");
693
694        assert_eq!(read_graph.node_count(), original_graph.node_count());
695        assert_eq!(read_graph.edge_count(), original_graph.edge_count());
696    }
697
698    #[test]
699    fn test_digraph_write_read_roundtrip() {
700        let mut original_graph: DiGraph<i32, f64> = DiGraph::new();
701        original_graph
702            .add_edge(1i32, 2i32, 1.5f64)
703            .expect("Test operation failed");
704        original_graph
705            .add_edge(2i32, 3i32, 2.0f64)
706            .expect("Test operation failed");
707
708        let temp_file = NamedTempFile::new().expect("Test operation failed");
709        write_dot_format_digraph(&original_graph, temp_file.path(), true)
710            .expect("Test operation failed");
711
712        let read_graph: DiGraph<i32, f64> =
713            read_dot_format_digraph(temp_file.path(), true).expect("Test operation failed");
714
715        assert_eq!(read_graph.node_count(), original_graph.node_count());
716        assert_eq!(read_graph.edge_count(), original_graph.edge_count());
717    }
718
719    #[test]
720    fn test_remove_comments() {
721        assert_eq!(
722            remove_comments("1 -- 2; // this is a comment", false),
723            ("1 -- 2; ".to_string(), false)
724        );
725        assert_eq!(
726            remove_comments("1 -- 2; /* comment */", false),
727            ("1 -- 2; ".to_string(), false)
728        );
729        assert_eq!(
730            remove_comments("no comments here", false),
731            ("no comments here".to_string(), false)
732        );
733    }
734
735    #[test]
736    fn test_parse_weight_from_attributes() {
737        let weight: f64 =
738            parse_weight_from_attributes("[weight=1.5]").expect("Test operation failed");
739        assert_eq!(weight, 1.5);
740
741        let weight: f64 = parse_weight_from_attributes("[label=\"edge\", weight=2.0]")
742            .expect("Test operation failed");
743        assert_eq!(weight, 2.0);
744
745        let weight: f64 =
746            parse_weight_from_attributes("[color=red]").expect("Test operation failed");
747        assert_eq!(weight, 0.0); // default
748    }
749
750    #[test]
751    fn test_invalid_dot_format() {
752        let mut temp_file = NamedTempFile::new().expect("Test operation failed");
753        writeln!(temp_file, "invalid format").expect("Test operation failed");
754        temp_file.flush().expect("Test operation failed");
755
756        let result: Result<Graph<i32, f64>> = read_dot_format(temp_file.path(), false);
757        assert!(result.is_err());
758    }
759}