bank-statement-rs 0.1.0

Rust library for parsing bank and credit card transaction history from multiple financial export formats
Documentation
# bank-statement-rs


bank-statement-rs is a Rust library for parsing bank and credit card transaction history from multiple common financial export formats.

## Features


- **Multiple Format Support**: QFX/OFX with extensible architecture for CSV and other formats
- **Auto-Detection**: Automatically detect file format from content and filename
- **Builder Pattern**: Fluent API for configuring and parsing
- **Type-Safe**: Strongly-typed transactions with chrono and rust_decimal
- **Serde Support**: Serialize/deserialize transactions and format configurations

## Supported Formats


- **QFX/OFX** (both XML 2.x and SGML 1.x formats)
- 🚧 **CSV** (planned)
- 🚧 **OFX** (planned - separate from QFX)

## Installation


Add this to your `Cargo.toml`:

```toml
[dependencies]
bank-statement-rs = "0.1.0"
```

## Usage


### Builder Pattern (Recommended)


The builder pattern provides a fluent and flexible API:

```rust
use bank_statement_rs::ParserBuilder;

// Auto-detect format with filename hint
let content = std::fs::read_to_string("statement.qfx")?;
let transactions = ParserBuilder::new()
    .content(&content)
    .filename("statement.qfx")
    .parse()?;

for tx in transactions {
    println!("{} | {} | {:?}", tx.date, tx.amount, tx.payee);
}
```

### Auto-detect without filename


```rust
use bank_statement_rs::ParserBuilder;

let transactions = ParserBuilder::new()
    .content(&content)
    .parse()?;
```

### Specify format explicitly


```rust
use bank_statement_rs::{FileFormat, ParserBuilder};

let transactions = ParserBuilder::new()
    .content(&content)
    .format(FileFormat::Qfx)
    .parse()?;
```

### API Methods


The `ParserBuilder` provides the following methods:

- **`.content(&str)`** - Set the file content to parse
- **`.filename(&str)`** - Set filename for format detection (optional)
- **`.format(FileFormat)`** - Explicitly set the format to skip auto-detection (optional)
- **`.parse()`** - Parse and return `Vec<Transaction>` (the default type)
- **`.parse_into::<T>()`** - Parse and return `Vec<T>` where `T: TryFrom<ParsedTransaction>`

## Architecture


Each parser outputs its **raw format-specific structures** wrapped in a `ParsedTransaction` enum:
- QFX/OFX → `ParsedTransaction::Qfx(QfxTransaction)`
- Future parsers will add their own variants

### Default Transaction Structure


The library provides a suggested `Transaction` struct with `TryFrom<ParsedTransaction>` implemented:

```rust
pub struct Transaction {
    pub date: NaiveDate,
    pub amount: Decimal,
    pub payee: Option<String>,
    pub transaction_type: String,          // e.g., "DEBIT", "CREDIT", "CHECK"
    pub fitid: Option<String>,              // Financial Institution Transaction ID
    pub status: Option<String>,
    pub memo: Option<String>,
}
```

### Custom Output Types


You can create your own transaction structure by implementing `TryFrom<ParsedTransaction>`:

```rust
use bank_statement_rs::{ParsedTransaction, ParserBuilder};

#[derive(Debug)]

struct MyTransaction {
    amount: f64,
    merchant: String,
    date: String,
}

impl TryFrom<ParsedTransaction> for MyTransaction {
    type Error = String;

    fn try_from(parsed: ParsedTransaction) -> Result<Self, Self::Error> {
        match parsed {
            ParsedTransaction::Qfx(qfx) => Ok(MyTransaction {
                amount: qfx.amount.to_string().parse().unwrap_or(0.0),
                merchant: qfx.name.unwrap_or_default(),
                date: format!("{:?}", qfx.dt_posted),
            }),
            // Handle other formats as needed
        }
    }
}

// Use parse_into to get your custom type
let my_transactions: Vec<MyTransaction> = ParserBuilder::new()
    .content(&content)
    .parse_into()?;
```

## Extending with Custom Parsers


Implement the `Parser` trait for your custom format. Each parser should output its own raw format structure:

```rust
use bank_statement_rs::Parser;
use serde::{Deserialize, Serialize};

// Define your raw format structure
#[derive(Debug, Clone, Serialize, Deserialize)]

pub struct CustomTransaction {
    pub date: String,
    pub amount: String,
    pub description: String,
}

pub struct CustomParser;

impl Parser for CustomParser {
    type Output = CustomTransaction;

    fn is_supported(filename: Option<&str>, content: &str) -> bool {
        // Check if this parser can handle the file
        filename.map(|f| f.ends_with(".custom")).unwrap_or(false)
    }

    fn parse(content: &str) -> Result<Vec<CustomTransaction>, String> {
        // Parse logic here - return your raw format structures
        Ok(vec![])
    }
}

// To integrate with the builder, follow these steps:
// 1. Add a variant to ParsedTransaction enum in src/builder.rs:
//    ParsedTransaction::Custom(CustomTransaction)
// 2. Add a variant to FileFormat enum in src/builder.rs:
//    FileFormat::Custom
// 3. Update FileFormat::parse() to handle the new format
// 4. Update auto-detection logic in ParserBuilder::parse_into()
// 5. Implement TryFrom<CustomTransaction> for Transaction (optional)
```

See [CLAUDE.md](CLAUDE.md) for detailed step-by-step instructions on adding new parsers.

## Examples


See the [examples](examples/) directory for more usage examples:

```bash
# Run with example data

cargo run --example parse_qfx

# Parse your own QFX file

cargo run --example parse_qfx path/to/your/statement.qfx
```

## License


This project is open source.