ibapi 3.0.1

A Rust implementation of the Interactive Brokers TWS API, providing a reliable and user friendly interface for TWS and IB Gateway. Designed with a focus on simplicity and performance.
Documentation
# Extending the API

This guide covers advanced topics for extending the rust-ibapi functionality.

## Anti-Patterns to Avoid

These examples demonstrate violations of principles in [code-style.md](code-style.md#design-principles).

### Duplicated Logic
```rust
// Bad: duplicated validation in sync and async
impl Client {
    pub fn my_func(&self, param: &str) -> Result<Data, Error> {
        if param.is_empty() { return Err(Error::InvalidParam); }
        // ...
    }
}
impl Client {
    pub async fn my_func(&self, param: &str) -> Result<Data, Error> {
        if param.is_empty() { return Err(Error::InvalidParam); }  // duplicate!
        // ...
    }
}
```

```rust
// Good: shared validation in common/
pub(crate) fn validate_param(param: &str) -> Result<(), Error> {
    if param.is_empty() { return Err(Error::InvalidParam); }
    Ok(())
}

// Usage in sync.rs and async.rs
validate_param(param)?;
```

### Monolithic Functions
```rust
// Bad: method does encoding, validation, and error handling
impl Client {
    pub fn place_order(&self, order: &Order) -> Result<(), Error> {
        // 100+ lines of mixed concerns
    }
}
```

```rust
// Good: split by responsibility
impl Client {
    pub fn place_order(&self, order: &Order) -> Result<(), Error> {
        validate_order(order)?;
        let request = encode_order(order)?;
        send_and_handle_response(self, request)
    }
}
```

### Large Parameter Lists
```rust
// Bad: 4+ params signal need for builder.
fn create_order(action: Action, qty: f64, price: f64, tif: TimeInForce,
                oca_group: String, oca_type: i32, cond: Option<Condition>) { }

// Good: fluent builder on Client.
client.order(&contract)
    .buy(qty)
    .limit(price)
    .time_in_force(tif)
    .oca_group(oca_group, oca_type)
    .condition(cond)
    .submit()
```

## Module Organization

Each API module follows a consistent structure to support both sync and async modes:

```
src/<module>/
├── mod.rs         # Public types and module exports
├── common/        # Shared implementation details
│   ├── mod.rs     # Export encoders/decoders
│   ├── encoders.rs # Message encoding functions
│   ├── decoders.rs # Message decoding functions
│   ├── test_tables.rs # Shared test cases (optional)
│   └── test_data.rs # Common test fixtures (optional)
├── sync.rs        # Synchronous implementation
└── async.rs       # Asynchronous implementation
```

## Module Structure Pattern

Follow this pattern when creating new modules:

```rust
// src/<module>/mod.rs
//! Module description

// Common implementation modules
mod common;

// Feature-specific implementations
#[cfg(feature = "sync")]
mod sync;

#[cfg(feature = "async")]
mod r#async;

// Public types - always available regardless of feature flags
#[derive(Debug)]
pub struct MyData {
    pub field: String,
}

```

## Adding New API Functionality

### Step 1: Define Public Types and API Interface

Define data types in the module's `mod.rs` file - these should be available regardless of feature flags. The API is exposed as `impl Client` methods in the domain module's `sync.rs` / `async.rs` files (not in `client/sync.rs` or `client/async.rs`).

### Step 2: Ensure Message Identifiers Are Defined

Make sure the appropriate incoming message and outgoing message identifiers are defined in `src/messages.rs`.

### Step 3: Update Message Type to Request ID Map

When processing messages received from TWS, the request ID needs to be determined. A map of message type to request ID position is maintained in `src/messages.rs` and may need to be updated.

### Step 4: Implement Shared Business Logic

Create the common implementation that both sync and async will use:

```rust
// src/<module>/common/encoders.rs
pub(in crate::<module>) fn encode_my_request(request_id: i32, param: &str) -> Result<RequestMessage, Error> {
    let mut message = RequestMessage::new();
    message.push_field(&OutgoingMessages::MyRequest);
    message.push_field(&request_id);
    message.push_field(param);
    Ok(message)
}

// src/<module>/common/decoders.rs
//
// Dispatch on wire format. On server_version >= PROTOBUF (201), TWS may send
// either text or protobuf for the same message type — text decoders run on a
// protobuf ResponseMessage will EOF on field 2, because the protobuf form
// carries only [msg_id_str] in `fields` and the payload lives in `raw_bytes`.
pub(in crate::<module>) fn decode_my_response(message: &mut ResponseMessage) -> Result<MyData, Error> {
    message.decode_proto_or_text(decode_my_response_proto, |msg| {
        msg.skip(); // message type
        msg.skip(); // request id (if present in this message type)
        Ok(MyData {
            field: msg.next_string()?,
        })
    })
}

pub(crate) fn decode_my_response_proto(bytes: &[u8]) -> Result<MyData, Error> {
    let p = crate::proto::MyResponse::decode(bytes)?;
    Ok(MyData {
        field: p.field.unwrap_or_default(),
    })
}
```

For `StreamDecoder::decode`, end the match with `_ => Err(Error::UnexpectedResponse(message.clone()))`. `process_decode_result` skip-classifies `UnexpectedResponse`; `NotImplemented` or `Simple` terminate the subscription on any unknown message type — that's the bug class of issue #508.

### Step 5: Implement Sync Version

Add an `impl Client` block in the domain module's `sync.rs`:

```rust
// src/<module>/sync.rs
use super::common::{encoders, decoders};
use crate::common::request_helpers;
use crate::client::sync::Client;

impl Client {
    pub fn my_function(&self, param: &str) -> Result<MyData, Error> {
        request_helpers::blocking::one_shot_with_retry(
            self,
            OutgoingMessages::MyRequest,
            || encoders::encode_my_request(self.next_request_id(), param),
            |message| decoders::decode_my_response(message),
            || Err(Error::UnexpectedEndOfStream),
        )
    }
}
```

### Step 6: Implement Async Version

Add an `impl Client` block in the domain module's `async.rs`:

```rust
// src/<module>/async.rs
use super::common::{encoders, decoders};
use crate::common::request_helpers;
use crate::Client;

impl Client {
    pub async fn my_function(&self, param: &str) -> Result<MyData, Error> {
        request_helpers::one_shot_with_retry(
            self,
            OutgoingMessages::MyRequest,
            || encoders::encode_my_request(self.next_request_id(), param),
            |message| decoders::decode_my_response(message),
            || Err(Error::UnexpectedEndOfStream),
        ).await
    }
}
```

No module re-exports needed — the `impl Client` methods are automatically available on the Client type.

### Step 8: Add Comprehensive Tests

Create table-driven tests that work for both sync and async:

```rust
// src/<module>/common/test_tables.rs
pub struct MyApiTestCase {
    pub name: &'static str,
    pub input: &'static str,
    pub expected_result: ApiExpectedResult,
}

pub const MY_API_TESTS: &[MyApiTestCase] = &[
    MyApiTestCase {
        name: "valid request",
        input: "test_input",
        expected_result: ApiExpectedResult::Success { /* ... */ },
    },
    // ... more test cases
];
```

### Step 9: Verify Both Modes

Test your implementation in both sync and async modes:

```bash
# Test sync implementation
cargo test <module>::sync --features sync
cargo clippy --features sync

# Test async implementation  
cargo test <module>::async --features async
cargo clippy --features async
```

### Step 10: Add Examples

Add examples showing the API usage to the examples folder:
- Sync examples: `examples/sync/my_feature.rs`
- Async examples: `examples/async/my_feature.rs`

Ensure examples are well-documented and demonstrate proper usage patterns.

## Common Utilities

The `src/common/` directory contains shared utilities used by both sync and async implementations:

### Error Helpers (`src/common/error_helpers.rs`)

Provides validation and error handling utilities:

```rust
use crate::common::error_helpers;

// Validate required parameters
let value = error_helpers::require(some_option, "parameter is required")?;
let request_id = error_helpers::require_request_id_for(request_id, "my operation")?;

// Validate ranges
let port = error_helpers::require_range(port, 1, 65535, "port")?;

// Validate with custom logic
let valid_value = error_helpers::require_with(some_option, || {
    "value must meet custom criteria".to_string()
})?;
```

### Request Helpers (`src/common/request_helpers.rs`)

Provides common request patterns for both sync and async modes:

```rust
use crate::common::request_helpers;

// For one-shot requests with retry logic (sync, inside impl Client)
pub fn my_api_call(&self) -> Result<MyData, Error> {
    request_helpers::blocking::one_shot_with_retry(
        self,
        OutgoingMessages::MyRequest,
        || encode_my_request(self.next_request_id()),
        |message| decode_my_response(message),
        || Err(Error::UnexpectedEndOfStream),
    )
}

// For one-shot requests with retry logic (async, inside impl Client)
pub async fn my_api_call(&self) -> Result<MyData, Error> {
    request_helpers::one_shot_with_retry(
        self,
        OutgoingMessages::MyRequest,
        || encode_my_request(self.next_request_id()),
        |message| decode_my_response(message),
        || Err(Error::UnexpectedEndOfStream),
    ).await
}

// For requests with IDs and subscriptions (inside impl Client)
pub fn my_subscription(&self) -> Result<Subscription<MyData>, Error> {
    request_helpers::blocking::request_with_id(self, Features::MY_FEATURE, |request_id| {
        encode_my_request(request_id)
    })
}
```

### Retry Logic (`src/common/retry.rs`)

Handles connection reset scenarios:

```rust
use crate::common::retry;

// Automatically retry on connection reset
let result = retry::retry_on_connection_reset(|| {
    // Your operation that might fail due to connection reset
    my_operation()
})?;
```