A comprehensive, modular JSON-RPC 2.0 implementation for Rust with multiple transport layers and extra features.
- Full implementation with requests, responses, notifications, and batch operations
- TCP, TCP streaming, WebSocket, HTTP via Axum, and Tower middleware
- Fluent API for constructing requests and responses
- Organize and dispatch JSON-RPC methods with automatic routing
- Generate OpenAPI/Swagger specifications from method definitions
- Efficient caching and optimized request handling
- Use only what you need with feature flags
- Support for context-aware method handlers
- CLI tool for generating boilerplate code
- Convenient macros for common response patterns
This workspace contains the following packages:
ash-rpc-core- Core JSON-RPC implementation with transport support, stateful handlers, and CLI toolash-rpc-contrib- Additional utilities and middleware (health checks, caching, etc.)examples- Comprehensive examples and demos
cargo add ash-rpc-core
# Optional: enable features as needed
cargo add ash-rpc-core --features stateful,websocket,axumuse ash_rpc_core::*;
fn main() {
// Create a method registry
let mut registry = MethodRegistry::new();
// Register a simple method
registry.register("ping", |_params, id| {
rpc_success!("pong", id)
});
// Register a method with parameters
registry.register("add", |params, id| {
let nums: Vec<i32> = serde_json::from_value(params.unwrap_or_default())?;
if nums.len() == 2 {
rpc_success!(nums[0] + nums[1], id)
} else {
rpc_error!(error_codes::INVALID_PARAMS, "Expected 2 numbers", id)
}
});
// Handle a request
let request = Request::new("ping", None, Some(RequestId::Number(1)));
let response = registry.call("ping", request.params, request.id);
println!("{}", serde_json::to_string_pretty(&response).unwrap());
}use ash_rpc_core::{MethodRegistry, transport::tcp::TcpServerBuilder};
use std::sync::Arc;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut registry = MethodRegistry::new();
registry.register("echo", |params, id| {
rpc_success!(params, id)
});
let server = TcpServerBuilder::new("127.0.0.1:8080")
.processor(Arc::new(registry))
.build()?;
println!("JSON-RPC server listening on 127.0.0.1:8080");
server.run()?;
Ok(())
}use ash_rpc_core::*;
use axum::{http::StatusCode, response::Json, routing::post, Router};
use std::sync::Arc;
#[tokio::main]
async fn main() {
let mut registry = MethodRegistry::new();
registry.register("greet", |params, id| {
let name: String = serde_json::from_value(params.unwrap_or_default())?;
rpc_success!(format!("Hello, {}!", name), id)
});
let registry = Arc::new(registry);
let app = Router::new()
.route("/rpc", post({
let registry = registry.clone();
move |Json(request): Json<Request>| async move {
let response = registry.call(&request.method, request.params, request.id);
(StatusCode::OK, Json(response))
}
}));
let listener = tokio::net::TcpListener::bind("127.0.0.1:3000").await.unwrap();
println!("JSON-RPC server listening on http://127.0.0.1:3000/rpc");
axum::serve(listener, app).await.unwrap();
}use ash_rpc_core::*;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let registry = MethodRegistry::new()
.register("ping", |_params, id| rpc_success!("pong", id))
.register("echo", |params, id| rpc_success!(params, id));
let server = transport::websocket::WebSocketServer::builder("127.0.0.1:9001")
.processor(registry)
.build()?;
println!("WebSocket JSON-RPC server on ws://127.0.0.1:9001");
server.run().await?;
Ok(())
}use ash_rpc_core::*;
use serde_json::json;
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut client = transport::websocket::WebSocketClientBuilder::new("ws://127.0.0.1:9001")
.connect()
.await?;
let request = RequestBuilder::new("ping")
.id(json!(1))
.build();
client.send_message(&Message::Request(request)).await?;
if let Some(response) = client.recv_response().await? {
println!("Response: {:?}", response);
}
Ok(())
}use ash_rpc_core::middleware::JsonRpcLayer;
use tower::{ServiceBuilder, Service};
use axum::{routing::post, Router, Json};
let middleware = ServiceBuilder::new()
.layer(JsonRpcLayer::new()
.validate_version(true)
.require_id(false))
.service(your_service);use ash_rpc_core::stateful::*;
use std::sync::{Arc, Mutex};
#[derive(Debug)]
struct AppError(String);
impl std::fmt::Display for AppError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.0)
}
}
impl std::error::Error for AppError {}
struct AppContext {
counter: Arc<Mutex<i32>>,
}
impl ServiceContext for AppContext {
type Error = AppError;
}
let context = AppContext {
counter: Arc::new(Mutex::new(0)),
};
let registry = StatefulMethodRegistry::new()
.register_fn("increment", |ctx: &AppContext, _params, id| {
let mut counter = ctx.counter.lock().unwrap();
*counter += 1;
rpc_success!(*counter, id)
});Generate OpenAPI/Swagger documentation from your JSON-RPC methods:
use ash_rpc_core::*;
let mut registry = MethodRegistry::new();
registry.register_with_docs(
"calculate",
"Performs mathematical calculations",
Some("Supports basic arithmetic operations"),
|params, id| {
// Implementation...
rpc_success!(42, id)
}
);
// Generate OpenAPI spec
let docs = registry.render_docs("Calculator API", "1.0.0");
println!("{}", docs);The examples/ directory contains comprehensive examples:
basic.rs- Simple method registration and callingtcp_server.rs- TCP server implementationaxum_server.rs- HTTP server with Axumtower_http_simple.rs- Tower middleware with HTTPtower_tcp_simple.rs- Tower middleware with TCPcalculator_engine.rs- Advanced calculator with macro usagestateful_server.rs- Stateful context examplesdocs_demo.rs- Documentation generationcaching_demo.rs- Performance optimization with caching
Configure the library with feature flags:
[dependencies]
ash-rpc-core = { version = "0.1.0", features = ["tcp", "tower", "docs"] }Available features:
tcp- TCP transport supporttcp-stream- TCP streaming supportwebsocket- WebSocket transport supportaxum- Axum HTTP server integrationtower- Tower middleware supportstateful- Stateful handlers with shared contextcli- Code generation CLI tool
Generate boilerplate code with the CLI tool:
# Install the CLI tool
cargo install ash-rpc-core --features cli
# Generate a new method implementation
ash-rpc-gen --method my_method --output src/my_method.rsThe library includes several performance optimizations:
- Efficient JSON parsing with serde
- Method dispatch caching for faster lookups
- Documentation caching to avoid regeneration
- Connection pooling for TCP streams
- Minimal allocations in hot paths
ash-rpc-core/
├── types.rs # Core JSON-RPC types
├── builders.rs # Fluent builders for requests/responses
├── traits.rs # Handler and processor traits
├── registry.rs # Method registration and dispatch
├── transport.rs # TCP and streaming transports
├── middleware.rs # Tower middleware integration
├── macros.rs # Convenience macros
└── utils.rs # Documentation and utility functions
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass:
cargo test - Submit a pull request
- MIT License (LICENSE-MIT)