# sproto-rust

A pure-Rust implementation of the [sproto](https://github.com/cloudwu/sproto) binary serialization protocol.

Sproto is a compact, schema-driven serialization format designed for simplicity and efficiency, similar to Protocol Buffers but with a smaller feature set.

## Features

- **Derive API** - `#[derive(SprotoEncode, SprotoDecode)]` for struct serialization with `to_bytes`/`from_bytes`
- **Direct API** - Tag-based `StructEncoder`/`StructDecoder` for field-by-field encoding/decoding
- **Binary schema loader** - Load pre-compiled binary schemas from C/Lua toolchain
- **Builder API** - Programmatic schema construction in pure Rust
- **Pack/Unpack** - Zero-packing compression for wire efficiency
- **RPC** - Request/response dispatch with session tracking
- **Lua binding** - `sproto-lua` crate exposes the Rust implementation as a Lua C module

## Installation

Add to your `Cargo.toml`:

```toml
[dependencies]
sproto-rust = "0.1"
```

The `derive` feature is enabled by default. To use only the Direct API without proc-macro dependencies:

```toml
[dependencies]
sproto-rust = { version = "0.1", default-features = false }
```

Note: The crate is published as `sproto-rust` on crates.io, but the library name is `sproto`, so you use `use sproto::...` in your code.

## Quick Start

### Derive API (Recommended)

The simplest way to use sproto — derive macros for struct serialization:

```rust
use sproto::{SprotoEncode, SprotoDecode};
use sproto::types::{Sproto, Field, FieldType};

#[derive(SprotoEncode, SprotoDecode)]
struct Person {
    #[sproto(tag = 0)]
    name: String,
    #[sproto(tag = 1)]
    age: i64,
    #[sproto(tag = 2)]
    active: bool,
}

// Build or load schema
let mut schema = Sproto::new();
schema.add_type("Person", vec![
    Field::new("name",   0, FieldType::String),
    Field::new("age",    1, FieldType::Integer),
    Field::new("active", 2, FieldType::Boolean),
]);

// Encode
let person = Person { name: "Alice".into(), age: 30, active: true };
let bytes = sproto::to_bytes(&schema, "Person", &person).unwrap();

// Decode
let decoded: Person = sproto::from_bytes(&schema, "Person", &bytes).unwrap();
assert_eq!(decoded.name, "Alice");
```

Supported field types:

| Rust Type | Sproto Type | Attribute |
|-----------|-------------|-----------|
| `i64`, `i32`, `i16`, `i8`, `u32`, `u16`, `u8` | `integer` | `#[sproto(tag = N)]` |
| `bool` | `boolean` | `#[sproto(tag = N)]` |
| `f64` | `double` | `#[sproto(tag = N)]` |
| `f64` (decimal) | `integer(N)` | `#[sproto(tag = N, decimal = M)]` |
| `String` | `string` | `#[sproto(tag = N)]` |
| `Vec<u8>` | `binary` | `#[sproto(tag = N)]` |
| `Vec<T>` | `*type` | `#[sproto(tag = N)]` |
| `Option<T>` | optional field | `#[sproto(tag = N)]` |
| `Box<T>` | nested struct | `#[sproto(tag = N)]` |
| Struct with derive | `.Type` | `#[sproto(tag = N)]` |

### Building Schema in Rust

Use the Builder API to construct schemas programmatically:

```rust
use sproto::types::{Sproto, Field, FieldType};

let mut schema = Sproto::new();
schema.add_type("Person", vec![
    Field::new("name",   0, FieldType::String),
    Field::new("age",    1, FieldType::Integer),
    Field::new("active", 2, FieldType::Boolean),
]);
```

### Loading Binary Schema

Load pre-compiled binary schemas generated by the C/Lua toolchain:

```rust
let schema_bytes = std::fs::read("schema.bin").unwrap();
let schema = sproto::binary_schema::load_binary(&schema_bytes).unwrap();
```

### Encoding and Decoding

Use `StructEncoder`/`StructDecoder` for tag-based encoding and decoding:

```rust
use sproto::codec::{StructEncoder, StructDecoder};

let st = schema.get_type("Person").unwrap();

// Encode
let mut buf = Vec::new();
let mut enc = StructEncoder::new(&schema, st, &mut buf);
enc.set_string(0, "Alice").unwrap();
enc.set_integer(1, 30).unwrap();
enc.set_boolean(2, true).unwrap();
enc.finish();

// Decode
let mut dec = StructDecoder::new(&schema, st, &buf).unwrap();
while let Some(field) = dec.next_field().unwrap() {
    match field.tag() {
        0 => println!("name = {}", field.as_string().unwrap()),
        1 => println!("age = {}", field.as_integer().unwrap()),
        2 => println!("active = {}", field.as_boolean().unwrap()),
        _ => {}
    }
}
```

### Pack/Unpack Compression

```rust
use sproto::pack;

// Compress encoded data for transmission
let packed = pack::pack(&encoded);

// Decompress after receiving
let unpacked = pack::unpack(&packed).unwrap();
```

### RPC

```rust
use sproto::rpc::{Host, DispatchResult};

let schema = sproto::binary_schema::load_binary(&schema_bytes).unwrap();

// Create RPC host
let mut host = Host::new(schema.clone());

// Dispatch incoming packet
match host.dispatch(&packed_data).unwrap() {
    DispatchResult::Request { name, body, responder, ud } => {
        // Handle request, send response
        if let Some(resp) = responder {
            let response = resp.respond(&response_body, None).unwrap();
        }
    }
    DispatchResult::Response { session, body, ud } => {
        // Handle response
    }
}
```

## Schema Syntax

Sproto schemas are text files compiled to binary format by the C/Lua toolchain:

```sproto
.Person {
    name 0 : string
    age 1 : integer
    active 2 : boolean
    score 3 : double
    photo 4 : binary
    fpn 5 : integer(2)      # Fixed-point decimal
    phone 6 : PhoneNumber    # Nested struct
    friends 7 : *Person     # Array of structs
    tags 8 : *string        # Array of strings
}

# Protocol definition (RPC)
login 1 {
    request LoginRequest
    response LoginResponse
}

ping 2 {
    response PingResponse    # No request body
}

logout 3 {
    response nil             # Confirm, no response data
}

notify 4 {
    # One-way, no response
}
```

## Type Mappings

| Sproto Type | Rust Type |
|-------------|-----------|
| `integer` | `i64` |
| `boolean` | `bool` |
| `string` | `String` / `&str` |
| `binary` | `Vec<u8>` / `&[u8]` |
| `double` | `f64` |
| `integer(N)` | `i64` (scaled by 10^N) |
| `*type` | `Vec<T>` |
| `.Type` | nested struct |

## Wire Protocol Compatibility

This implementation is wire-compatible with the [reference C/Lua implementation](https://github.com/cloudwu/sproto). Binary data encoded by either implementation can be decoded by the other.

## Documentation

- [Design](docs/design.md) - Architecture and implementation details
- [Usage Guide](docs/usage.md) - Detailed usage guide
- [Development](docs/development.md) - Development guide

## License

MIT

## Acknowledgments

- [cloudwu/sproto](https://github.com/cloudwu/sproto) - Original C/Lua implementation
