# Design Specification
This document details the design of the `runnel` library, aligning with the acceptance criteria derived from the source code.
## 1. Core Architecture: Pluggable I/O
The library is designed around a central concept of abstract, swappable I/O streams. The core architecture allows an application to be decoupled from the specific source of its input and destination of its output/error streams. This is achieved through a set of public traits that define the capabilities of a stream, and a container struct that holds concrete implementations of these traits.
This design directly satisfies the core requirement for a pluggable I/O system (**U1**, **O1**).
## 2. Core Components
### 2.1. I/O Traits
The foundation of the library is a set of three traits that abstract I/O operations:
* `trait StreamIn`: Represents a readable input stream. It provides methods for reading bytes (`lock_bufread`) and iterating over lines (`lines`).
* `trait StreamOut`: Represents a writable output stream. It provides methods for writing bytes (`lock`) and handling line-based writing (`write_line`, `flush_line`).
* `trait StreamErr`: Represents a writable error stream, with a parallel interface to `StreamOut`.
These traits ensure that any custom I/O implementation will be compatible with the system, satisfying the extensibility requirement (**U2**).
### 2.2. `RunnelIoe` Struct
This is the main container struct that an application interacts with. It holds three boxed trait objects:
```rust
// Simplified
pub struct RunnelIoe {
pg_in: Box<dyn StreamIn>,
pg_out: Box<dyn StreamOut>,
pg_err: Box<dyn StreamErr>,
}
```
By using trait objects, `RunnelIoe` can hold any struct that implements the required I/O traits, providing the core of the pluggable architecture.
### 2.3. `RunnelIoeBuilder`
To simplify the creation of `RunnelIoe` instances, a builder pattern is implemented (**O1**). `RunnelIoeBuilder` allows a user to optionally specify implementations for each of the three streams. If any stream is not specified, it defaults to the standard I/O implementation upon calling `.build()`. This directly addresses criterion **E1**.
```rust
// Usage
let sioe = RunnelIoeBuilder::new()
.pg_in(MyCustomInput::new())
.build(); // pg_out and pg_err default to stdout/stderr
```
## 3. Stream Implementations (The `medium` module)
The library provides four concrete implementations of the I/O traits.
### 3.1. `stdio`
This module provides thin wrappers around `std::io::stdin`, `std::io::stdout`, and `std::io::stderr`. `StdIn`, `StdOut`, and `StdErr` structs implement the corresponding `StreamIn`, `StreamOut`, and `StreamErr` traits. This is the default implementation, ensuring out-of-the-box usability.
### 3.2. `stringio`
This implementation uses in-memory `String` and `Vec<u8>` buffers to simulate I/O streams (`StringIn`, `StringOut`). This is crucial for testing application logic without depending on a live console, satisfying **S1** and **O2**.
### 3.3. `pipeio`
This module implements an in-memory, thread-safe byte pipe. It uses a `std::sync::mpsc::sync_channel` to send `Vec<u8>` chunks between threads. `PipeOut` is the sender and `PipeIn` is the receiver. This design fulfills the requirements for inter-thread byte-stream communication (**S2**, **O3**, **E3**).
### 3.4. `linepipeio`
Similar to `pipeio`, this module also uses an `mpsc::sync_channel`, but it is optimized for sending `String` objects line-by-line. This avoids the overhead of parsing a byte stream back into lines on the receiving end, offering a more performant and convenient API for line-based protocols. This design fulfills the requirements for inter-thread line-based communication (**S3**, **O4**, **E4**).
## 4. Concurrency and Thread Safety
Thread safety is a primary concern, especially for the `pipeio` and `linepipeio` modules. The design addresses this in two ways:
1. **Channel-based Communication**: The use of `mpsc` channels for pipes is inherently thread-safe, as these channels are designed for single-consumer, multiple-producer message passing.
2. **Mutex Guards**: For other shared resources, the library uses `std::sync::Mutex` to protect shared data. The `lock()` methods on the traits return a guard object, ensuring that the lock is held for the duration of the I/O operation and automatically released afterward.
This locking strategy ensures that concurrent access to streams is safe, satisfying criterion **U3**.