# Error Handling Mechanisms in graphviz2drawio: Custom Exceptions and CLI Recovery

> Explore graphviz2drawio error handling: custom exceptions, validation, and CLI recovery for robust GraphViz to DrawIO conversions. Learn how it ensures smooth data transformation.

- Repository: [Harold Martin/graphviz2drawio](https://github.com/hbmartin/graphviz2drawio)
- Tags: deep-dive
- Published: 2026-03-03

---

**`graphviz2drawio` implements a layered error-handling strategy combining a custom exception hierarchy, defensive validation checks, and user-friendly CLI error reporting to ensure robust GraphViz-to-DrawIO conversion.**

The `hbmartin/graphviz2drawio` library converts GraphViz DOT files into DrawIO-compatible MXGraph format. Understanding the error handling mechanisms in graphviz2drawio is essential for both library consumers integrating the Python API and command-line users processing batch conversions. The codebase employs specific custom exceptions defined in [`graphviz2drawio/models/Errors.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/models/Errors.py) alongside strategic try/except blocks to manage malformed SVG data, invalid GraphViz output, and encoding issues.

## Custom Exception Hierarchy

All conversion-specific errors inherit from the base class **`GdValueError`**, defined in [`graphviz2drawio/models/Errors.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/models/Errors.py). This design allows callers to catch any graphviz2drawio-specific failure using a single `except GdValueError:` clause while still permitting granular exception handling.

The library defines six specific exception types for distinct failure modes:

- **`CouldNotParsePathError`** – Raised when an SVG element contains unparsable path data in its `d` attribute (lines 13–19)
- **`MissingTitleError`** – Triggered when a `<g>` element representing a node, edge, or cluster lacks a required title comment (lines 22–28)
- **`MissingTextError`** – Indicates a text element is missing required attributes (lines 31–37)
- **`InvalidBezierParameterError`** – Occurs when Bézier subdivision receives a parameter `t` outside the valid range `[0, 1]` (lines 40–45)
- **`MissingIdentifiersError`** – Raised when a node or edge SVG element lacks both an `id` attribute and a title (lines 47–53)
- **`UnableToParseGraphError`** – Signals that `pygraphviz.AGraph.draw()` returned `None`, indicating invalid GraphViz output (lines 56–62)

## Defensive Checks and Explicit Raises

Throughout the core conversion pipeline, functions employ defensive programming with explicit `raise` statements to fail fast and provide specific context about validation failures.

### Node and Edge Validation

In **`graphviz2drawio/mx/NodeFactory.from_svg`**, the factory validates that both `sid` (SVG `id`) and `gid` (title) are present before creating a node object. If either identifier is missing, it immediately raises `MissingIdentifiersError(sid, gid)` to guarantee every node has a unique reference.

Similarly, **`graphviz2drawio/models/SvgParser.parse_nodes_edges_clusters`** iterates through SVG group elements and ensures each contains a title comment. When a group lacks this required metadata, it raises `MissingTitleError(g)` to prevent silent data loss during conversion.

### Geometric and Graph Validation

The Bézier curve processing in **`graphviz2drawio/mx/bezier.subdivide`** validates that the subdivision parameter `t` falls within the inclusive range `[0, 1]`. An illegal value triggers `InvalidBezierParameterError(t)`, stopping curve corruption before it propagates to the output geometry.

The main entry point **`graphviz2drawio/graphviz2drawio.convert`** verifies that GraphViz successfully generated SVG output. When `pygraphviz.AGraph.draw()` returns `None`, the function raises `UnableToParseGraphError(graph)` to indicate the input DOT file is invalid or GraphViz execution failed.

### Path Parsing Protection

When **`graphviz2drawio/mx/RectFactory.rect_from_svg_path`** encounters malformed path data, the resulting `CouldNotParsePathError(path_d)` provides the exact offending path string, enabling precise debugging of SVG generation issues.

## Top-Level CLI Error Handling

The command-line interface in [`graphviz2drawio/__main__.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/__main__.py) implements comprehensive error handling to surface user-friendly messages while maintaining diagnostic detail.

### Conversion Wrapper

The `convert()` call is wrapped in a try/except block that handles specific encoding and unexpected failures:

```python
try:
    output = convert(to_convert, program)
except UnicodeDecodeError:
    # Automatic UTF-8 retry for Windows-specific encoding issues

    return _convert_file(...)
except Exception:
    _write_stderr_message(str(to_convert))
    raise

```

This structure catches `UnicodeDecodeError` to implement an automatic fallback to UTF-8 encoding (lines 46–53). For all other exceptions, it invokes `_write_stderr_message` (lines 24–31) to print a formatted error block containing the Python version, library version, and input file path before re-raising the exception to ensure a non-zero exit status.

### Argument Validation

The `_validate_args` function performs pre-flight checks on command-line arguments, verifying mutually exclusive options like `--stdout` and `--outfile`, and confirming required input files exist. Upon validation failure, it prints a clear error message and calls `sys.exit(1)` (lines 16–23).

## Graceful Degradation for Edge Cases

Not all errors halt execution. The utility function **`adjust_color_opacity`** in [`graphviz2drawio/mx/utils.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/mx/utils.py) demonstrates graceful degradation by wrapping hex color parsing in a try/except block:

```python
try:
    # Parse and adjust hex color opacity

    ...
except ValueError:
    return hex_string  # Return original on parse failure

```

When a color string cannot be parsed as valid hexadecimal, the function returns the original string rather than crashing, allowing the conversion to continue with potentially malformed but non-critical styling attributes.

## Code Examples

### Catching Specific Conversion Errors

```python
from graphviz2drawio.models.Errors import MissingTitleError, GdValueError
from graphviz2drawio.graphviz2drawio import convert
from pygraphviz import AGraph

dot = "digraph { a -> b }"
graph = AGraph(string=dot)

try:
    mx_xml = convert(graph)
except MissingTitleError as exc:
    print(f"Title missing in SVG element: {exc}")
except GdValueError as exc:
    print(f"Graph conversion failed: {exc}")

```

### Handling CLI Errors Programmatically

```python
import subprocess

result = subprocess.run(
    ["graphviz2drawio", "example.dot"],
    capture_output=True,
    text=True,
)

if result.returncode != 0:
    print("Conversion failed:")
    print(result.stderr)  # Contains formatted error from _write_stderr_message

```

### Validating Bézier Parameters

```python
from graphviz2drawio.mx.bezier import subdivide
from graphviz2drawio.models.Errors import InvalidBezierParameterError

try:
    left, right = subdivide(p1, c1, c2, p2, t=1.5)
except InvalidBezierParameterError as exc:
    print(f"Invalid curve parameter: {exc}")

```

## Summary

- **Custom exception hierarchy** centered on `GdValueError` in [`graphviz2drawio/models/Errors.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/models/Errors.py) enables both broad and granular error catching
- **Defensive validation** throughout `NodeFactory`, `SvgParser`, and `bezier` modules raises specific exceptions like `MissingIdentifiersError` and `InvalidBezierParameterError` at the point of failure
- **CLI resilience** in [`graphviz2drawio/__main__.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/__main__.py) provides automatic UTF-8 fallback for encoding errors and structured stderr reporting via `_write_stderr_message`
- **Graceful degradation** in utility functions like `adjust_color_opacity` allows conversion to continue despite malformed non-critical data
- **Pre-flight validation** via `_validate_args` ensures argument errors are caught before conversion begins with clear `sys.exit(1)` termination

## Frequently Asked Questions

### What is the base exception class for all graphviz2drawio errors?

All custom exceptions in graphviz2drawio inherit from **`GdValueError`**, defined in [`graphviz2drawio/models/Errors.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/models/Errors.py). This base class allows you to catch any conversion-specific error using `except GdValueError:` while still permitting handlers for specific subclasses like `MissingTitleError` or `UnableToParseGraphError`.

### How does graphviz2drawio handle file encoding issues?

The CLI entry point in [`graphviz2drawio/__main__.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/__main__.py) specifically catches `UnicodeDecodeError` and automatically retries the conversion using UTF-8 encoding. This handles Windows-specific encoding issues without requiring user intervention. If the retry fails, the error propagates to the generic exception handler which writes a detailed error message to stderr.

### Can I catch all graphviz2drawio conversion errors with a single except clause?

Yes. Since all custom exceptions inherit from `GdValueError`, you can use a single `except GdValueError as exc:` clause to catch any conversion-related failure including `CouldNotParsePathError`, `MissingIdentifiersError`, and `UnableToParseGraphError`. This is the recommended approach for library consumers who want to handle all graphviz2drawio-specific errors uniformly.

### What happens when GraphViz produces invalid or empty output?

When `pygraphviz.AGraph.draw()` returns `None`—indicating GraphViz could not render the graph—the `convert()` function in [`graphviz2drawio/graphviz2drawio.py`](https://github.com/hbmartin/graphviz2drawio/blob/main/graphviz2drawio/graphviz2drawio.py) raises **`UnableToParseGraphError`**. This provides immediate feedback that the input DOT syntax is invalid or GraphViz installation/configuration is faulty, rather than proceeding with null data.