### Rust JSON RPC Client Example Source: https://github.com/roboplc/roboplc-rpc/blob/main/README.md Demonstrates how to create and use an RpcClient in Rust for sending JSON RPC requests. It defines custom request and response enums for method calls and results, and shows the basic flow of creating a request and handling a response. ```rust use serde::{Serialize, Deserialize}; use roboplc_rpc::{client::RpcClient, dataformat}; #[derive(Serialize, Deserialize)] // use tag = "method", content = "params" for the canonical JSON-RPC 2.0 #[serde(tag = "m", content = "p", rename_all = "lowercase", deny_unknown_fields)] enum MyMethod<'a> { Test {}, Hello { name: &'a str }, } #[derive(Serialize, Deserialize)] #[serde(untagged)] enum MyResult { General { ok: bool }, String(String), } let client: RpcClient = RpcClient::new(); let req = client.request(MyMethod::Hello { name: "world" }).unwrap(); // send req.payload() via the chosen transport to the server // if response is received, get the result // let result = client.handle_response(&response); // returns MyResult or RpcError ``` -------------------------------- ### Rust JSON RPC Server Example Source: https://github.com/roboplc/roboplc-rpc/blob/main/README.md Illustrates setting up a JSON RPC server in Rust using roboplc_rpc. It defines a handler struct that implements the RpcServerHandler trait to process incoming method calls and return results. The example shows how to instantiate the server and handle incoming request payloads. ```rust use serde::{Serialize, Deserialize}; use roboplc_rpc::{RpcResult, dataformat, server::{RpcServer, RpcServerHandler}}; // use the same types as in the client, e.g. share a common crate #[derive(Serialize, Deserialize)] #[serde(tag = "m", content = "p", rename_all = "lowercase", deny_unknown_fields)] enum MyMethod<'a> { Test {}, Hello { name: &'a str }, } #[derive(Serialize, Deserialize)] #[serde(untagged)] enum MyResult { General { ok: bool }, String(String), } struct MyRpc {} impl<'a> RpcServerHandler<'a> for MyRpc { type Method = MyMethod<'a>; type Result = MyResult; type Source = std::net::IpAddr; fn handle_call(&self, method: MyMethod, source: Self::Source) -> RpcResult { println!("Received call from {}", source); match method { MyMethod::Test {} => Ok(MyResult::General { ok: true }), MyMethod::Hello { name } => Ok(MyResult::String(format!("Hello, {}", name))), } } } let server = roboplc_rpc::server::RpcServer::new(MyRpc {}); // get the request from the transport let request_payload = r#"{"i":1,"m":"hello","p":{"name":"world"}}"#.as_bytes(); if let Some(response_payload) = server.handle_request_payload::( request_payload, std::net::IpAddr::V4(std::net::Ipv4Addr::LOCALHOST)) { // send response_payload via the chosen transport to the client. if the // payload is none, either the client does not need a response or the // request was completely invalid (no error can be returned) } ``` -------------------------------- ### Rust Client-Server RPC Communication Source: https://context7.com/roboplc/roboplc-rpc/llms.txt Demonstrates a full client-server RPC setup in Rust using RoboPLC RPC. It defines shared enums for methods and results, implements the server handler, and simulates network calls to test various RPC functionalities like ping, echo, calculations, and error handling. ```rust use std::net::{IpAddr, Ipv4Addr}; use serde::{Serialize, Deserialize}; use roboplc_rpc::{ client::RpcClient, server::{RpcServer, RpcServerHandler}, dataformat::Json, RpcResult, RpcError, RpcErrorKind, }; // Shared types between client and server #[derive(Serialize, Deserialize, Debug)] #[serde(tag = "m", content = "p", rename_all = "lowercase", deny_unknown_fields)] enum Method<'a> { Ping {}, Echo { message: &'a str }, Calculate { op: &'a str, a: i32, b: i32 }, } #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] enum Result { Ok { ok: bool }, Text(String), Number(i32), } // Server implementation struct Calculator; impl<'a> RpcServerHandler<'a> for Calculator { type Method = Method<'a>; type Result = Result; type Source = IpAddr; fn handle_call(&self, method: Method, _source: Self::Source) -> RpcResult { match method { Method::Ping {} => Ok(Result::Ok { ok: true }), Method::Echo { message } => Ok(Result::Text(message.to_string())), Method::Calculate { op, a, b } => match op { "add" => Ok(Result::Number(a + b)), "sub" => Ok(Result::Number(a - b)), "mul" => Ok(Result::Number(a * b)), "div" if b != 0 => Ok(Result::Number(a / b)), "div" => Err(RpcError::new( RpcErrorKind::Custom(-32001), "Division by zero".into(), )), _ => Err(RpcError::new( RpcErrorKind::InvalidParams, format!("Unknown operation: {}", op), )), }, } } } fn main() { // Setup server and client let server = RpcServer::new(Calculator); let client: RpcClient = RpcClient::new(); let source = IpAddr::V4(Ipv4Addr::LOCALHOST); // Helper to simulate network round-trip let call = |req: &roboplc_rpc::client::RpcClientRequest| { server.handle_request_payload::(req.payload(), source) }; // Test ping let req = client.request(Method::Ping {}).unwrap(); let resp = call(&req).unwrap(); println!("Ping: {:?}", req.handle_response(&resp)); // Output: Ping: Ok(Ok { ok: true }) // Test echo let req = client.request(Method::Echo { message: "Hello!" }).unwrap(); let resp = call(&req).unwrap(); println!("Echo: {:?}", req.handle_response(&resp)); // Output: Echo: Ok(Text("Hello!")) // Test calculation let req = client.request(Method::Calculate { op: "add", a: 10, b: 5 }).unwrap(); let resp = call(&req).unwrap(); println!("10 + 5 = {:?}", req.handle_response(&resp)); // Output: 10 + 5 = Ok(Number(15)) // Test error handling let req = client.request(Method::Calculate { op: "div", a: 10, b: 0 }).unwrap(); let resp = call(&req).unwrap(); match req.handle_response(&resp) { Ok(r) => println!("Unexpected: {:?}", r), Err(e) => println!("Error: {} - {:?}", e.kind(), e.message()), } // Output: Error: Custom(-32001) - Some("Division by zero") } ``` -------------------------------- ### Parse and Convert RPC with HTTP QueryString and HttpResponse in Rust Source: https://context7.com/roboplc/roboplc-rpc/llms.txt Demonstrates parsing an RPC request from a URL query string using `QueryString` and converting an RPC response into an HTTP response with headers and body using `HttpResponse`. This is useful for handling GET requests and formatting responses for HTTP clients. ```rust use roboplc_rpc::request::Request; use roboplc_rpc::response::{Response, HandlerResponse}; use roboplc_rpc::tools::http::{QueryString, HttpResponse}; use serde::{Serialize, Deserialize}; #[derive(Serialize, Deserialize)] #[serde(tag = "method", content = "params")] // Canonical format for HTTP enum Method { #[serde(rename = "get_user")] GetUser { id: u32 }, #[serde(rename = "search")] Search { query: String, limit: u32, }, } #[derive(Serialize, Deserialize)] struct User { id: u32, name: String, } fn main() -> Result<(), Box> { // Parse RPC request from query string (HTTP GET) // Format: i=&m=&=&... let qs = QueryString::new("i=1&m=get_user&id=42"); let request: Request = qs.try_into()?; // Convert request to query string for HTTP GET let request = Request::new(serde_json::json!(2), Method::Search { query: "rust rpc".to_string(), limit: 10, }); let qs: QueryString = request.try_into()?; println!("Query string: {}", qs); // Output: i=2&m=search&query=rust+rpc&limit=10 // Convert RPC response to HTTP response let rpc_response: Response = Response::from_handler_response( serde_json::json!(1), HandlerResponse::Ok(User { id: 42, name: "Alice".to_string() }), ); let http_response: HttpResponse = rpc_response.try_into()?; println!("HTTP Status: {}", http_response.status()); // Output: HTTP Status: 200 OK println!("Content-Type: {:?}", http_response.headers().get("content-type")); // Output: Content-Type: Some("application/json") println!("X-JSONRPC-ID: {:?}", http_response.headers().get("x-jsonrpc-id")); // Output: X-JSONRPC-ID: Some("1") println!("Body: {}", http_response.body()); // Output: Body: {"r":{"id":42,"name":"Alice"}} // Split into parts for framework integration let (status, headers, body) = http_response.into_parts(); Ok(()) } ``` -------------------------------- ### Handle RPC Requests with RpcServer and RpcServerHandler in Rust Source: https://context7.com/roboplc/roboplc-rpc/llms.txt Demonstrates setting up an RPC server that processes incoming requests by delegating them to a handler implementing the RpcServerHandler trait. It covers defining request methods, result types, and handling request sources. The server automatically manages request parsing, error responses, and serialization. ```rust use std::net::{IpAddr, Ipv4Addr}; use serde::{Serialize, Deserialize}; use roboplc_rpc::{RpcResult, RpcError, RpcErrorKind, dataformat}; use roboplc_rpc::server::{RpcServer, RpcServerHandler}; // Define methods matching the client's definition #[derive(Serialize, Deserialize)] #[serde(tag = "m", content = "p", rename_all = "lowercase", deny_unknown_fields)] enum MyMethod<'a> { Test {}, Hello { name: &'a str }, Add { a: i32, b: i32 }, } #[derive(Serialize, Deserialize)] #[serde(untagged)] enum MyResult { General { ok: bool }, String(String), Number(i32), } // Implement the RPC handler struct MyRpcHandler; impl<'a> RpcServerHandler<'a> for MyRpcHandler { type Method = MyMethod<'a>; type Result = MyResult; type Source = IpAddr; // Track where requests come from fn handle_call(&self, method: MyMethod, source: Self::Source) -> RpcResult { println!("Received call from {}", source); match method { MyMethod::Test {} => Ok(MyResult::General { ok: true }), MyMethod::Hello { name } => Ok(MyResult::String(format!("Hello, {}!", name))), MyMethod::Add { a, b } => Ok(MyResult::Number(a + b)), } } } fn main() { let handler = MyRpcHandler; let server = RpcServer::new(handler); // Simulate receiving a request payload let request_payload = r#"{"i":1,"m":"hello","p":{"name":"Alice"}}"#.as_bytes(); let source = IpAddr::V4(Ipv4Addr::new(192, 168, 1, 100)); // Process the request and get the response if let Some(response) = server.handle_request_payload::(request_payload, source) { println!("Response: {}", std::str::from_utf8(&response).unwrap()); // Output: {"i":1,"r":"Hello, Alice!"} } // Handle an invalid request gracefully let invalid_request = r#"{"i":2,"m":"unknown_method","p":{}}"#.as_bytes(); if let Some(response) = server.handle_request_payload::(invalid_request, source) { println!("Error response: {}", std::str::from_utf8(&response).unwrap()); // Output: {"i":2,"e":{"code":-32601,"message":"..."}} } // Notification (no id) returns no response let notification = r#"{"m":"test","p":{}}"#.as_bytes(); let response = server.handle_request_payload::(notification, source); assert!(response.is_none()); // No response for notifications } ``` -------------------------------- ### Rust RpcClient: Create and Send JSON-RPC Requests Source: https://context7.com/roboplc/roboplc-rpc/llms.txt Demonstrates how to use the `RpcClient` in Rust to create type-safe JSON-RPC requests and notifications. It covers defining methods and results using enums with serde attributes, serializing requests, and handling typed responses or errors. ```rust use serde::{Serialize, Deserialize}; use roboplc_rpc::{client::RpcClient, dataformat, RpcResult}; // Define the method enum with serde attributes for JSON-RPC format #[derive(Serialize, Deserialize)] #[serde(tag = "m", content = "p", rename_all = "lowercase", deny_unknown_fields)] enum MyMethod<'a> { Test {}, Hello { name: &'a str }, Add { a: i32, b: i32 }, } // Define the result enum to handle different response types #[derive(Serialize, Deserialize, Debug)] #[serde(untagged)] enum MyResult { General { ok: bool }, String(String), Number(i32), } fn main() -> Result<(), Box> { // Create a typed RPC client with JSON format let client: RpcClient = RpcClient::new(); // Create a request with parameters let req = client.request(MyMethod::Hello { name: "world" })?; // Get the serialized payload to send via your transport let payload = req.payload(); println!("Request payload: {}", std::str::from_utf8(payload)?); // Output: {"i":0,"m":"hello","p":{"name":"world"}} // Simulate receiving a response from the server let response_bytes = r#"{"i":0,"r":"Hello, world"}"#.as_bytes(); // Parse the response with type safety let result: RpcResult = req.handle_response(response_bytes); match result { Ok(MyResult::String(msg)) => println!("Success: {}", msg), Ok(other) => println!("Unexpected result: {:?}", other), Err(e) => println!("RPC Error: {} - {}", e.kind(), e.message()), } // Create a notification (no response expected) let notification = client.request0(MyMethod::Test {})?; println!("Notification: {}", std::str::from_utf8(notification.payload())?); // Output: {"m":"test","p":{}} Ok(()) } ``` -------------------------------- ### Create and Handle JSON-RPC 2.0 Request/Response Objects in Rust Source: https://context7.com/roboplc/roboplc-rpc/llms.txt Illustrates how to construct and deconstruct JSON-RPC 2.0 Request and Response objects using the 'roboplc_rpc' crate. This is useful for custom transport layers or detailed message control. It covers creating requests, notifications, success responses, and error responses. Dependencies include 'serde' and 'roboplc_rpc'. ```rust use serde::{Serialize, Deserialize}; use roboplc_rpc::request::Request; use roboplc_rpc::response::{Response, HandlerResponse}; use roboplc_rpc::{RpcError, RpcErrorKind}; #[derive(Serialize, Deserialize)] #[serde(tag = "m", content = "p")] enum Method { #[serde(rename = "ping")] Ping {}, } #[derive(Serialize, Deserialize)] struct PingResult { pong: bool, } fn main() { // Create a request with ID let request: Request = Request::new( serde_json::json!(1), // Request ID Method::Ping {}, ); // Create a notification (no ID, no response expected) let notification: Request = Request::new0(Method::Ping {}); // Decompose request into parts let (id, method) = request.into_parts(); println!("Request ID: {:?}", id); // Reconstruct from parts let reconstructed: Request = Request::from_parts(Some(serde_json::json!(2)), Method::Ping {}); // Create success response let success_response: Response = Response::from_handler_response( serde_json::json!(1), HandlerResponse::Ok(PingResult { pong: true }), ); // Create error response let error_response: Response = Response::from_server_error( serde_json::json!(1), "Something went wrong".to_string(), ); // Check response type let (id, handler_res) = success_response.into_parts(); match handler_res { HandlerResponse::Ok(result) => println!("Success: pong={}", result.pong), HandlerResponse::Err(e) => println!("Error: {:?}", e.message()), } // Output: Success: pong=true } ``` -------------------------------- ### Implement JSON Serialization with DataFormat Trait in Rust Source: https://context7.com/roboplc/roboplc-rpc/llms.txt Shows how to use the DataFormat trait, specifically the Json implementation, for serializing and deserializing data structures in Rust. This allows for pluggable serialization formats, with JSON being the default and MessagePack available via a feature flag. Custom formats can be implemented by providing pack and unpack methods. ```rust use serde::{Serialize, Deserialize}; use roboplc_rpc::dataformat::{DataFormat, Json}; #[derive(Serialize, Deserialize, Debug, PartialEq)] struct Message { id: u32, content: String, } fn main() -> Result<(), Box> { let msg = Message { id: 42, content: "Hello, RPC!".to_string(), }; // Serialize with JSON format let packed: Vec = Json::pack(&msg)?; println!("Packed JSON: {}", std::str::from_utf8(&packed)?); // Output: {"id":42,"content":"Hello, RPC!"} // Deserialize back let unpacked: Message = Json::unpack(&packed)?; assert_eq!(msg, unpacked); println!("Unpacked: {:?}", unpacked); Ok(()) } // With msgpack feature enabled: // use roboplc_rpc::dataformat::Msgpack; // let packed: Vec = Msgpack::pack(&msg)?; // let unpacked: Message = Msgpack::unpack(&packed)?; ``` -------------------------------- ### Handle JSON-RPC 2.0 Errors with RpcError and RpcErrorKind in Rust Source: https://context7.com/roboplc/roboplc-rpc/llms.txt Demonstrates how to create, return, and handle JSON-RPC 2.0 errors using RpcError and RpcErrorKind in Rust. Covers standard and custom error codes, including error messages and their conversion to numeric codes. Dependencies include the 'roboplc_rpc' crate. ```rust use roboplc_rpc::{RpcError, RpcErrorKind, RpcResult}; fn divide(a: i32, b: i32) -> RpcResult { if b == 0 { // Return a custom error with code and message return Err(RpcError::new( RpcErrorKind::Custom(-32000), "Division by zero is not allowed".to_string(), )); } Ok(a / b) } fn validate_params(value: i32) -> RpcResult { if value < 0 { // Return a standard InvalidParams error without message return Err(RpcError::new0(RpcErrorKind::InvalidParams)); } Ok(value * 2) } fn main() { // Handle successful result match divide(10, 2) { Ok(result) => println!("10 / 2 = {}", result), Err(e) => println!("Error: {:?}", e), } // Output: 10 / 2 = 5 // Handle custom error match divide(10, 0) { Ok(result) => println!("Result: {}", result), Err(e) => { println!("Error code: {:?}", e.kind()); println!("Error message: {:?}", e.message()); // Convert error kind to i16 code let code: i16 = e.kind().into(); println!("Numeric code: {}", code); } } // Output: Error code: Custom(-32000) // Output: Error message: Some("Division by zero is not allowed") // Output: Numeric code: -32000 // Standard error codes let parse_err = RpcError::new0(RpcErrorKind::ParseError); let code: i16 = parse_err.kind().into(); assert_eq!(code, -32700); let internal_err = RpcError::new( RpcErrorKind::InternalError, "Database connection failed".to_string(), ); println!("Internal error: {}", internal_err); // Output: Internal error: Database connection failed (-32603) } ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.