tarpc-cat 0.1.0

RPC framework built on comp-cat-rs: typed effects, no async, categorical foundations
Documentation
# tarpc-cat

RPC framework built on [comp-cat-rs](https://crates.io/crates/comp-cat-rs): typed effects, no async, categorical foundations.

Reimagines the core abstractions of [tarpc](https://crates.io/crates/tarpc) using `Io<E, A>` as the effect type, blocking I/O via `Io::suspend`, and thread-based concurrency.  Nothing executes until `.run()`.

## Features

- **Lazy effects** -- all operations return `Io<Error, A>`, composable via `map`, `flat_map`, `zip`
- **Linear state-threading transport** -- the `Transport` trait consumes and returns itself on each operation, avoiding shared state
- **Connection-per-request client** -- simple, stateless TCP calls
- **Thread-per-connection server** -- accepts connections via iterator combinators, spawns a thread for each
- **Pluggable transport** -- `TcpTransport` for production, `ChannelTransport` for testing
- **Length-delimited JSON wire format** -- 4-byte big-endian length prefix + JSON payload
- **No async, no tokio** -- pure `std::net` + `comp-cat-rs` effects

## Usage

### Define a service

```rust
use tarpc_cat::serve::Serve;
use tarpc_cat::error::Error;
use comp_cat_rs::effect::io::Io;
use serde::{Serialize, Deserialize};

#[derive(Serialize, Deserialize)]
struct Ping(String);

#[derive(Serialize, Deserialize)]
struct Pong(String);

#[derive(Clone)]
struct PingService;

impl Serve for PingService {
    type Request = Ping;
    type Response = Pong;

    fn handle(&self, request: Ping) -> Io<Error, Pong> {
        Io::pure(Pong(request.0))
    }
}
```

### Run the server

```rust
use tarpc_cat::server::{serve, ListenAddr};

let addr = ListenAddr::new("127.0.0.1:9000".parse().unwrap());
serve(addr, PingService).run()?;
```

### Call from a client

```rust
use tarpc_cat::client::{call, ServerAddr};

let addr = ServerAddr::new("127.0.0.1:9000".parse().unwrap());
let pong: Pong = call(addr, Ping("hello".into())).run()?;
```

### Compose multiple calls

```rust
use tarpc_cat::client::{call, ServerAddr};

let addr = ServerAddr::new("127.0.0.1:9000".parse().unwrap());

let both = call::<Ping, Pong>(addr, Ping("first".into()))
    .flat_map(move |pong1| {
        call::<Ping, Pong>(addr, Ping("second".into()))
            .map(move |pong2| (pong1, pong2))
    });

let (a, b) = both.run()?;
```

### Use the transport directly

```rust
use tarpc_cat::client::call_on;
use tarpc_cat::transport::TcpTransport;

let transport = TcpTransport::connect("127.0.0.1:9000".parse().unwrap()).run()?;
let (response, transport) = call_on::<_, Ping, Pong>(transport, Ping("hello".into())).run()?;
// transport is available for another call
let (response2, _) = call_on::<_, Ping, Pong>(transport, Ping("again".into())).run()?;
```

## Architecture

```text
client.rs      call() and call_on() -- connection-per-request RPC
server.rs      serve() -- bind + accept loop + thread-per-connection
serve.rs       Serve trait -- Clone + Send + 'static, handle returns Io
transport.rs   Transport trait -- linear state-threading, TcpTransport, ChannelTransport
codec.rs       Length-delimited JSON framing over Read/Write
protocol.rs    Wire types: RequestId newtype, Envelope sum type
error.rs       Error enum with hand-rolled Display/Error/From
```

## Building

```bash
cargo build
```

## Testing

```bash
cargo test
```

## Linting

```bash
RUSTFLAGS="-D warnings" cargo clippy
```

## Documentation

```bash
cargo doc --no-deps --open
```

Docs are auto-published to GitHub Pages on push to `main` via the workflow in `.github/workflows/docs.yml`.  Enable in repo Settings > Pages > Source > GitHub Actions.

## License

Licensed under either of

- [Apache License, Version 2.0]http://www.apache.org/licenses/LICENSE-2.0
- [MIT License]http://opensource.org/licenses/MIT

at your option.