cmark-writer 0.6.2

A CommonMark writer implementation in Rust for serializing AST nodes to CommonMark format
Documentation
# Error Handling

cmark-writer provides a comprehensive error handling system that helps you identify and resolve issues when generating Markdown content. This page covers the error types, handling strategies, and how to create custom errors.

## Error Types

The library uses the `WriteError` enum as its primary error type:

```rust
pub enum WriteError {
    // Standard errors
    NewlineInInlineElement(String),
    InvalidNesting(String),
    UnsupportedNodeType,
    IoError(std::io::Error),
    // Custom errors
    CustomError(Box<dyn CustomErrorFactory>),
    StructureError(Box<dyn StructureError>),
    CodedError(Box<dyn CodedError>),
}
```

Common error scenarios include:

- **Invalid content**: Like newlines in inline elements (`NewlineInInlineElement`)
- **Improper nesting**: When nodes are nested incorrectly (`InvalidNesting`)
- **Unsupported operations**: When trying to use unsupported features (`UnsupportedNodeType`)
- **I/O errors**: When writing to files or streams fails (`IoError`)
- **Custom errors**: Application-specific errors you define (`CustomError`, `StructureError`, `CodedError`)

## Basic Error Handling

When using the writer, you can handle errors with standard Rust patterns:

```rust
use cmark_writer::ast::Node;
use cmark_writer::writer::CommonMarkWriter;

let node = Node::Text("Hello".to_string());
let mut writer = CommonMarkWriter::new();

match writer.write(&node) {
    Ok(_) => {
        println!("Successfully wrote node");
        let output = writer.into_string();
        // Use the output...
    },
    Err(err) => {
        eprintln!("Failed to write node: {}", err);
        // Handle the error appropriately...
    }
}
```

You can also use the `?` operator for more concise error handling:

```rust
fn write_document(document: &Node) -> Result<String, cmark_writer::error::WriteError> {
    let mut writer = CommonMarkWriter::new();
    writer.write(document)?;
    Ok(writer.into_string())
}
```

## Creating Custom Errors

cmark-writer provides several ways to define your own error types:

### 1. Structure Errors

Use the `#[structure_error]` attribute macro for simple formatted errors:

```rust
use cmark_writer::custom_error;
use cmark_writer::WriteError;

// Define a structure error with format string
#[structure_error(format = "Table structure error: {}")]
struct TableStructureError(pub &'static str);

// Using the error
fn validate_table_rows(rows: &[Vec<Node>]) -> Result<(), WriteError> {
    if rows.is_empty() {
        return Err(TableStructureError("Table must have at least one row").into());
    }
    // More validation...
    Ok(())
}
```

### 2. Coded Errors

Use the `#[coded_error]` attribute macro for errors with error codes:

```rust
use cmark_writer::coded_error;
use cmark_writer::WriteError;

// Define a coded error with error code
#[coded_error]
struct ValidationError(pub String, pub String);

// Using the error
fn validate_content(content: &str) -> Result<(), WriteError> {
    if content.contains("<script>") {
        return Err(ValidationError("Content contains unsafe script tags".to_string(), 
                                 "UNSAFE_CONTENT_ERROR".to_string()).into());
    }
    Ok(())
}
```

### 3. Custom Error Factory

For more complex errors, implement the `CustomErrorFactory` trait:

```rust
use std::fmt;
use cmark_writer::error::{CustomErrorFactory, WriteError};

struct ComplexError {
    message: String,
    line: usize,
    column: usize,
}

impl fmt::Display for ComplexError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "Error at line {}, column {}: {}", 
               self.line, self.column, self.message)
    }
}

impl fmt::Debug for ComplexError {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        fmt::Display::fmt(self, f)
    }
}

impl CustomErrorFactory for ComplexError {
    fn message(&self) -> &str {
        &self.message
    }
    
    fn construct_error(self: Box<Self>) -> WriteError {
        WriteError::CustomError(self)
    }
}

// Using the error
fn process_at_position(content: &str, line: usize, column: usize) -> Result<(), WriteError> {
    if content.is_empty() {
        let error = ComplexError {
            message: "Empty content is not allowed".to_string(),
            line,
            column,
        };
        return Err(WriteError::CustomError(Box::new(error)));
    }
    Ok(())
}
```

## Result Extensions

The library provides the `WriteResultExt` trait with useful methods for working with results:

```rust
use cmark_writer::error::WriteResultExt;

fn process_content() -> Result<(), WriteError> {
    let result = possibly_failing_operation()
        .context("Failed during content processing")?;
    
    Ok(())
}
```

## Error Conversion

The library's error types implement `From` for easy conversion:

```rust
// Converting from std::io::Error
fn write_to_file(content: &str, path: &str) -> Result<(), WriteError> {
    let mut file = std::fs::File::create(path)
        .map_err(WriteError::from)?;
    
    // More operations...
    Ok(())
}
```

## Best Practices

1. **Be specific**: Use the most specific error type for each situation
2. **Add context**: Include helpful details in error messages
3. **Propagate appropriately**: Use `?` to propagate errors when appropriate
4. **Handle gracefully**: Provide meaningful recovery options when possible
5. **Test error paths**: Ensure your error handling logic works correctly