searpc 0.1.3

Rust client for Searpc RPC framework (Seafile-compatible)
Documentation

Searpc-rs: Rust implementation of libsearpc RPC protocol

A minimal, type-safe Rust implementation of the Searpc RPC protocol, compatible with libsearpc C and pysearpc Python implementations.

Design Philosophy ("Good Taste" Code)

This implementation follows Linus Torvalds' principles of good code:

  1. Data structures first: Use enums instead of strcmp()

    • C: if (strcmp(type, "int") == 0) ...
    • Rust: enum Arg { Int(i32), String(String), ... }
  2. Eliminate special cases: No edge-case handling

    • Single code path for all operations
    • Use ? operator for uniform error handling
  3. Memory safety by design: Zero unsafe code

    • Automatic RAII vs manual g_free()
    • Compiler-verified lifetime management

Protocol Format

Request (JSON array)

["function_name", arg1, arg2, ...]

Response (JSON object)

{
  "ret": <value>,
  "err_code": <int> (optional),
  "err_msg": <string> (optional)
}

Transport Protocols

libsearpc has two different packet protocols:

1. TCP Demo Protocol (16-bit header)

  • Used by: C demo server, simple testing
  • Header: 16-bit big-endian length (max 64KB)
  • Format: <u16 len><json>
  • Implementation: [TcpTransport]

2. Unix Socket Protocol (32-bit header + wrapper)

  • Used by: Seafile production, pysearpc
  • Header: 32-bit native-endian length
  • Format: <u32 len>{"service": "name", "request": [...]}
  • Implementation: [UnixSocketTransport]

Critical: Do not mix these protocols! Seafile requires Unix Socket protocol.

API Comparison

C (libsearpc)

// 10+ lines of parameter marshalling with strcmp()
if (strcmp(type, "int") == 0)
    json_array_append_new(array, json_integer((int)(long)value));
else if (strcmp(type, "string") == 0)
    json_array_add_string_or_null_element(array, (char *)value);
// ... more strcmp() branches

Rust (searpc-rs)

# use searpc::{SearpcClient, TcpTransport, Arg};
# let transport = TcpTransport::connect("127.0.0.1:12345").unwrap();
# let mut client = SearpcClient::new(transport);
// Type-safe, zero branches
let result = client.call_int("func", vec![
    Arg::int(42),
    Arg::string("hello"),
]);

Example Usage

Synchronous API

use searpc::{SearpcClient, TcpTransport, Arg};

# fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect via TCP (demo compatibility)
let transport = TcpTransport::connect("127.0.0.1:12345")?;
let mut client = SearpcClient::new(transport);

// Call RPC function with type safety
let length: i32 = client.call_int("strlen", vec![
    Arg::string("hello world")
])?;

println!("Length: {}", length);
# Ok(())
# }

Async API (with tokio)

use searpc::{AsyncSearpcClient, AsyncTcpTransport, Arg};

# #[tokio::main]
# async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Connect via async TCP
let transport = AsyncTcpTransport::connect("127.0.0.1:12345").await?;
let mut client = AsyncSearpcClient::new(transport);

// Call RPC function asynchronously
let length: i32 = client.call_int("strlen", vec![
    Arg::string("hello world")
]).await?;

println!("Length: {}", length);
# Ok(())
# }

Feature Completeness vs C Implementation

Implemented:

  • All 6 RPC types: int, int64, string, object, objlist, json
  • Both transport protocols (TCP 16-bit + Unix Socket 32-bit)
  • NULL parameter support (via Arg::Null)
  • Error handling (matches C's TRANSPORT_ERROR_CODE 500)
  • Type-safe API with compile-time checking

Async Support (optional, enabled by default):

  • Async API with tokio runtime
  • [AsyncSearpcClient] for async operations
  • [AsyncTcpTransport] for async TCP
  • Disable with default-features = false

Future (not needed for basic usage):

  • Connection pooling
  • Procedural macros for convenience
  • Server implementation

Code Metrics

  • 745 lines vs C's ~2000 lines (-63%)
  • Zero unsafe blocks
  • Zero compilation warnings
  • 14 unit tests (all passing)
  • 100% C compatibility (verified with demo server)