From 95b6227a4882c4ec63e3bf940f5ae9fd2279427c Mon Sep 17 00:00:00 2001 From: Steven Roose Date: Sun, 6 Mar 2022 08:35:18 +0000 Subject: [PATCH] REFACTOR: Change design to be central on the Request struct --- Cargo.toml | 3 +- src/client.rs | 447 ++++++++++++++++++++++++++++++++++----------- src/error.rs | 178 ++---------------- src/json.rs | 323 ++++++++++++++++++++++++++++++++ src/lib.rs | 164 +---------------- src/simple_http.rs | 47 +++-- src/simple_tcp.rs | 63 ++++--- src/simple_uds.rs | 85 +++++---- 8 files changed, 800 insertions(+), 510 deletions(-) create mode 100644 src/json.rs diff --git a/Cargo.toml b/Cargo.toml index 867c6ea5..30032b91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "jsonrpc" -version = "0.14.1" +version = "0.15.0-rc0" authors = ["Andrew Poelstra "] license = "CC0-1.0" homepage = "https://github.com/apoelstra/rust-jsonrpc/" @@ -34,6 +34,7 @@ proxy = ["socks"] [dependencies] serde = { version = "1", features = ["derive"] } serde_json = { version = "1", features = [ "raw_value" ] } +erased-serde = "0.3.20" base64 = { version = "0.13.0", optional = true } socks = { version = "0.3.4", optional = true} diff --git a/src/client.rs b/src/client.rs index 6f79cb65..9c42a1a5 100644 --- a/src/client.rs +++ b/src/client.rs @@ -18,146 +18,394 @@ //! and parsing responses //! +use std::fmt; use std::borrow::Cow; use std::collections::HashMap; -use std::fmt; +use std::error::Error as StdError; +use std::future::Future; use std::sync::atomic; use serde; -use serde_json; +use serde_json::Value; use serde_json::value::RawValue; -use super::{Request, Response}; +use crate::json; use crate::error::Error; -use crate::util::HashableValue; + +/// Error type of converter methods. +pub type ConverterError = Box; + +/// Conversion method that parses the JSON into a serde object. +pub fn convert_parse(raw: Box) -> Result +where + T: for<'a> serde::Deserialize<'a>, +{ + Ok(serde_json::from_str(raw.get())?) +} + +/// Trivial conversion method that actually doesn't do a conversion and keeps the [RawValue]. +pub fn convert_raw(raw: Box) -> Result, ConverterError> { + Ok(raw) +} /// An interface for a transport over which to use the JSONRPC protocol. -pub trait Transport: Send + Sync + 'static { +pub trait SyncTransport: fmt::Debug { + /// Send an RPC request over the transport. + fn send_request(&self, request: &json::Request) -> Result; + + /// Send a batch of RPC requests over the transport. + fn send_batch(&self, requests: &[json::Request]) -> Result, Error>; +} + +/// NB It is advised to also (usually trivially) implement SyncTransport +/// for AsyncTransports by blocking on the future. +pub trait AsyncTransport: fmt::Debug { /// Send an RPC request over the transport. - fn send_request(&self, _: Request) -> Result; + fn send_request( + &self, + request: &json::Request, + ) -> Box> + Unpin>; + /// Send a batch of RPC requests over the transport. - fn send_batch(&self, _: &[Request]) -> Result, Error>; - /// Format the target of this transport. - /// I.e. the URL/socket/... - fn fmt_target(&self, f: &mut fmt::Formatter) -> fmt::Result; + fn send_batch( + &self, + requests: &[json::Request], + ) -> Box, Error>> + Unpin>; +} + +/// A single parameter used in [Params]. +pub enum Param<'a> { + /// A [serde_json::Value] parameter. + Value(Value), + /// A serializable object by reference. + ByRef(&'a dyn erased_serde::Serialize), + /// A boxed serializable object. + InBox(Box), + /// A boxed [serde_json::value::RawValue]. + Raw(Box), +} + +impl<'a> serde::Serialize for Param<'a> { + fn serialize(&self, serializer: S) -> Result { + match self { + Param::Value(ref v) => serde::Serialize::serialize(v, serializer), + Param::ByRef(r) => serde::Serialize::serialize(r, serializer), + Param::InBox(b) => serde::Serialize::serialize(b, serializer), + Param::Raw(r) => serde::Serialize::serialize(r, serializer), + } + } +} + +impl From for Param<'static> { + fn from(v: Value) -> Param<'static> { + Param::Value(v) + } +} + +impl<'a, T: serde::Serialize> From<&'a T> for Param<'a> { + fn from(v: &'a T) -> Param<'a> { + Param::ByRef(v) + } +} + +impl From> for Param<'static> { + fn from(v: Box) -> Param<'static> { + Param::InBox(v) + } +} + +/// A list that can be either borrowed or owned. +/// +/// NB This enum is non-exhaustive and should not be matched over. +pub enum List<'a, T> { + /// A borrowed list in the form of a slice. + Slice(&'a [T]), + /// An owned list in the form of a boxed slice. + Boxed(Box<[T]>), + //TODO(stevenroose) add smallvec type or a N-size array maybe support different-N as features +} + +impl<'a, T> List<'a, T> { + /// Represent the list as a slice. + pub fn as_slice(&self) -> &[T] { + match self { + List::Slice(s) => s, + List::Boxed(v) => &v[..], + } + } +} + +/// Parameters passed into a RPC request. +pub enum Params<'a> { + /// Positional arguments. + ByPosition(List<'a, Param<'a>>), + /// Named arguments. + ByName(List<'a, (&'a str, Param<'a>)>), +} + +impl<'a> serde::Serialize for Params<'a> { + fn serialize(&self, serializer: S) -> Result { + match self { + Params::ByPosition(params) => params.as_slice().serialize(serializer), + Params::ByName(params) => { + let params = params.as_slice(); + let mut map = serializer.serialize_map(Some(params.len()))?; + for (key, value) in params.iter() { + serde::ser::SerializeMap::serialize_entry(&mut map, key, value)?; + } + serde::ser::SerializeMap::end(map) + }, + } + } +} + +impl<'a> From<&'a [Param<'a>]> for Params<'a> { + fn from(p: &'a [Param<'a>]) -> Params<'a> { + Params::ByPosition(List::Slice(p)) + } +} + +impl<'a> From]>> for Params<'a> { + fn from(p: Box<[Param<'a>]>) -> Params<'a> { + Params::ByPosition(List::Boxed(p)) + } +} + +impl<'a> From>> for Params<'a> { + fn from(p: Vec>) -> Params<'a> { + p.into_boxed_slice().into() + } +} + +impl<'a> From<&'a [(&'static str, Param<'a>)]> for Params<'a> { + fn from(p: &'a [(&'static str, Param<'a>)]) -> Params<'a> { + Params::ByName(List::Slice(p)) + } +} + +impl<'a> From)]>> for Params<'a> { + fn from(p: Box<[(&'static str, Param<'a>)]>) -> Params<'a> { + Params::ByName(List::Boxed(p)) + } +} + +impl<'a> From)>> for Params<'a> { + fn from(p: Vec<(&'static str, Param<'a>)>) -> Params<'a> { + p.into_boxed_slice().into() + } +} + +impl<'a> From>> for Params<'a> { + fn from(p: HashMap<&'static str, Param<'a>>) -> Params<'a> { + Params::ByName(List::Boxed(p.into_iter().collect())) + } +} + +/// A prepared RPC request ready to be made using a JSON-RPC client. +pub struct Request<'r, R: 'static> { + /// The RPC call method name. + pub method: Cow<'r, str>, + /// The parameters for the RPC call.. + pub params: Params<'r>, + /// A converter function to convert the resulting JSON response + /// into the desired response type. + pub converter: &'r dyn Fn(Box) -> Result, +} + +impl<'r, R> Request<'r, R> { + /// Validate the raw response object. + fn validate_response(nonce: &Value, response: &json::Response) -> Result<(), Error> { + if response.jsonrpc != None && response.jsonrpc != Some(From::from("2.0")) { + return Err(Error::VersionMismatch); + } + if response.id != *nonce { + return Err(Error::NonceMismatch); + } + Ok(()) + } + + /// Batch this request + pub fn batch(self, batch: &mut Batch<'r, R>) -> Result<(), Request<'r, R>> { + batch.insert_request(self) + } + + /// Execute this request by blocking. + pub fn get_sync(self, client: &Client) -> Result { + let req = client.create_raw_request_object(&self.method, &self.params); + let res = SyncTransport::send_request(&client.transport, &req)?; + Self::validate_response(&req.id, &res)?; + (self.converter)(res.into_raw_result()?).map_err(Error::ResponseConversion) + } + + /// Execute this request asynchronously. + pub async fn get_async(self, client: &Client) -> Result { + let req = client.create_raw_request_object(&self.method, &self.params); + let res = AsyncTransport::send_request(&client.transport, &req).await?; + Self::validate_response(&req.id, &res)?; + (self.converter)(res.into_raw_result()?).map_err(Error::ResponseConversion) + } +} + +/// A batch of multiple JSON-RPC requests. +pub struct Batch<'b, R: 'static> { + method: Option>, + converter: Option<&'b dyn Fn(Box) -> Result>, + /// List of arguments for the requests. + batch_args: Vec>, +} + +impl<'b, R> Batch<'b, R> { + /// Inserts the request into the batch if it is compatible. + /// If not, it returns the request in the Err variant. + pub fn insert_request(&mut self, req: Request<'b, R>) -> Result<(), Request<'b, R>> { + if let Some(method) = self.method.as_ref() { + if method.as_ref() != req.method.as_ref() || !std::ptr::eq(self.converter.unwrap(), req.converter) { + return Err(req); + } + } else { + self.method = Some(req.method); + self.converter = Some(req.converter); + } + + self.batch_args.push(req.params); + Ok(()) + } } /// A JSON-RPC client. /// -/// Create a new Client using one of the transport-specific constructors e.g., -/// [`Client::simple_http`] for a bare-minimum HTTP transport. -pub struct Client { - pub(crate) transport: Box, +/// Create a new Client using one of the transport-specific constructors: +/// - [Client::simple_http] for the built-in bare-minimum HTTP transport +pub struct Client { + transport: T, nonce: atomic::AtomicUsize, } -impl Client { - /// Creates a new client with the given transport. - pub fn with_transport(transport: T) -> Client { +impl Client { + /// Create a new [Client] using the given transport. + pub fn new(transport: T) -> Client { Client { - transport: Box::new(transport), + transport: transport, nonce: atomic::AtomicUsize::new(1), } } - /// Builds a request. + /// Creates a raw request object. /// /// To construct the arguments, one can use one of the shorthand methods - /// [`crate::arg`] or [`crate::try_arg`]. - pub fn build_request<'a>(&self, method: &'a str, params: &'a [Box]) -> Request<'a> { + /// [jsonrpc::arg] or [jsonrpc::try_arg]. + pub fn create_raw_request_object<'a>( + &self, + method: &'a str, + params: &'a Params<'a>, + ) -> json::Request<'a> { let nonce = self.nonce.fetch_add(1, atomic::Ordering::Relaxed); - Request { - method, - params, - id: serde_json::Value::from(nonce), + json::Request { + method: method, + params: params, + id: Value::from(nonce), jsonrpc: Some("2.0"), } } - /// Sends a request to a client. - pub fn send_request(&self, request: Request) -> Result { - self.transport.send_request(request) + pub fn prepare<'r, R>( + &self, + method: impl Into>, + params: impl Into>, + converter: &'r dyn Fn(Box) -> Result, + ) -> Request<'r, R> { + Request { + method: method.into(), + params: params.into(), + converter: converter, + } } - /// Sends a batch of requests to the client. - /// - /// Note that the requests need to have valid IDs, so it is advised to create the requests - /// with [`Client::build_request`]. - /// - /// # Returns - /// - /// The return vector holds the response for the request at the corresponding index. If no - /// response was provided, it's [`None`]. - pub fn send_batch(&self, requests: &[Request]) -> Result>, Error> { - if requests.is_empty() { - return Err(Error::EmptyBatch); + pub fn prepare_raw<'r>( + &self, + method: impl Into>, + params: impl Into>, + ) -> Request<'r, Box> { + Request { + method: method.into(), + params: params.into(), + converter: &convert_raw, } + } - // If the request body is invalid JSON, the response is a single response object. - // We ignore this case since we are confident we are producing valid JSON. - let responses = self.transport.send_batch(requests)?; - if responses.len() > requests.len() { - return Err(Error::WrongBatchResponseSize); + pub fn prepare_parse<'r, R: for<'a> serde::de::Deserialize<'a>>( + &self, + method: impl Into>, + params: impl Into>, + ) -> Request<'r, R> { + Request { + method: method.into(), + params: params.into(), + converter: &convert_parse, } + } - //TODO(stevenroose) check if the server preserved order to avoid doing the mapping + ///// Sends a batch of requests to the client. The return vector holds the response + ///// for the request at the corresponding index. If no response was provided, it's [None]. + ///// + ///// Note that the requests need to have valid IDs, so it is advised to create the requests + ///// with [build_request]. + //pub fn send_batch(&self, requests: &[json::Request]) -> Result>, Error> { + // if requests.is_empty() { + // return Err(Error::EmptyBatch); + // } - // First index responses by ID and catch duplicate IDs. - let mut by_id = HashMap::with_capacity(requests.len()); - for resp in responses.into_iter() { - let id = HashableValue(Cow::Owned(resp.id.clone())); - if let Some(dup) = by_id.insert(id, resp) { - return Err(Error::BatchDuplicateResponseId(dup.id)); - } - } - // Match responses to the requests. - let results = - requests.iter().map(|r| by_id.remove(&HashableValue(Cow::Borrowed(&r.id)))).collect(); - - // Since we're also just producing the first duplicate ID, we can also just produce the - // first incorrect ID in case there are multiple. - if let Some(id) = by_id.keys().next() { - return Err(Error::WrongBatchResponseId((*id.0).clone())); - } + // // If the request body is invalid JSON, the response is a single response object. + // // We ignore this case since we are confident we are producing valid JSON. + // let responses = self.transport.send_batch(requests)?; + // if responses.len() > requests.len() { + // return Err(Error::WrongBatchResponseSize); + // } - Ok(results) - } + // //TODO(stevenroose) check if the server preserved order to avoid doing the mapping + + // // First index responses by ID and catch duplicate IDs. + // let mut by_id = HashMap::with_capacity(requests.len()); + // for resp in responses.into_iter() { + // let id = HashableValue(Cow::Owned(resp.id.clone())); + // if let Some(dup) = by_id.insert(id, resp) { + // return Err(Error::BatchDuplicateResponseId(dup.id)); + // } + // } + // // Match responses to the requests. + // let results = requests.into_iter().map(|r| { + // by_id.remove(&HashableValue(Cow::Borrowed(&r.id))) + // }).collect(); + + // // Since we're also just producing the first duplicate ID, we can also just produce the + // // first incorrect ID in case there are multiple. + // if let Some((id, _)) = by_id.into_iter().nth(0) { + // return Err(Error::WrongBatchResponseId(id.0.into_owned())); + // } + // Ok(results) + //} +} + +impl Client { /// Make a request and deserialize the response. /// /// To construct the arguments, one can use one of the shorthand methods - /// [`crate::arg`] or [`crate::try_arg`]. - pub fn call serde::de::Deserialize<'a>>( - &self, - method: &str, - args: &[Box], + /// [jsonrpc::arg] or [jsonrpc::try_arg]. + pub fn call_sync<'s, R: 'static + for<'a> serde::de::Deserialize<'a>>( + &'s self, + method: &'static str, + params: Vec>, ) -> Result { - let request = self.build_request(method, args); - let id = request.id.clone(); - - let response = self.send_request(request)?; - if response.jsonrpc.is_some() && response.jsonrpc != Some(From::from("2.0")) { - return Err(Error::VersionMismatch); - } - if response.id != id { - return Err(Error::NonceMismatch); - } - - response.result() + self.prepare_parse(method, params).get_sync(self) } } -impl fmt::Debug for crate::Client { +impl fmt::Debug for Client { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "jsonrpc::Client(")?; - self.transport.fmt_target(f)?; - write!(f, ")") - } -} - -impl From for Client { - fn from(t: T) -> Client { - Client::with_transport(t) + write!(f, "jsonrpc::Client(nonce: {}; transport: {:?})", + self.nonce.load(atomic::Ordering::Relaxed), self.transport, + ) } } @@ -165,18 +413,13 @@ impl From for Client { mod tests { use super::*; use std::sync; + use json; + #[derive(Debug)] struct DummyTransport; - impl Transport for DummyTransport { - fn send_request(&self, _: Request) -> Result { - Err(Error::NonceMismatch) - } - fn send_batch(&self, _: &[Request]) -> Result, Error> { - Ok(vec![]) - } - fn fmt_target(&self, _: &mut fmt::Formatter) -> fmt::Result { - Ok(()) - } + impl SyncTransport for DummyTransport { + fn send_request(&self, _: json::Request) -> Result { Err(Error::NonceMismatch) } + fn send_batch(&self, _: &[json::Request]) -> Result, Error> { Ok(vec![]) } } #[test] diff --git a/src/error.rs b/src/error.rs index 490cff71..af1c6244 100644 --- a/src/error.rs +++ b/src/error.rs @@ -17,19 +17,20 @@ //! Some useful methods for creating Error objects //! -use std::{error, fmt}; +use std::fmt; -use serde::{Deserialize, Serialize}; use serde_json; -use crate::Response; +use crate::json::RpcError; /// A library error #[derive(Debug)] #[non_exhaustive] pub enum Error { + /// The transport used doesn't support the sync/async method. + NoTransportSupport, /// A transport error - Transport(Box), + Transport(Box), /// Json error Json(serde_json::Error), /// Error response @@ -46,6 +47,8 @@ pub enum Error { BatchDuplicateResponseId(serde_json::Value), /// Batch response contained an ID that didn't correspond to any request ID WrongBatchResponseId(serde_json::Value), + /// Error occurred in converting the response value into the return type. + ResponseConversion(Box), } impl From for Error { @@ -63,6 +66,7 @@ impl From for Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match *self { + Error::NoTransportSupport => write!(f, "used transport doesn't support the sync/async method"), Error::Transport(ref e) => write!(f, "transport error: {}", e), Error::Json(ref e) => write!(f, "JSON decode error: {}", e), Error::Rpc(ref r) => write!(f, "RPC error response: {:?}", r), @@ -74,16 +78,18 @@ impl fmt::Display for Error { Error::VersionMismatch => write!(f, "`jsonrpc` field set to non-\"2.0\""), Error::EmptyBatch => write!(f, "batches can't be empty"), Error::WrongBatchResponseSize => write!(f, "too many responses returned in batch"), + Error::ResponseConversion(ref e) => write!(f, "response conversion error: {}", e), } } } -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { use self::Error::*; match *self { Rpc(_) + | NoTransportSupport | NonceMismatch | VersionMismatch | EmptyBatch @@ -92,166 +98,8 @@ impl error::Error for Error { | WrongBatchResponseId(_) => None, Transport(ref e) => Some(&**e), Json(ref e) => Some(e), + ResponseConversion(ref e) => Some(&**e), } } } -/// Standard error responses, as described at at -/// -/// -/// # Documentation Copyright -/// Copyright (C) 2007-2010 by the JSON-RPC Working Group -/// -/// This document and translations of it may be used to implement JSON-RPC, it -/// may be copied and furnished to others, and derivative works that comment -/// on or otherwise explain it or assist in its implementation may be prepared, -/// copied, published and distributed, in whole or in part, without restriction -/// of any kind, provided that the above copyright notice and this paragraph -/// are included on all such copies and derivative works. However, this document -/// itself may not be modified in any way. -/// -/// The limited permissions granted above are perpetual and will not be revoked. -/// -/// This document and the information contained herein is provided "AS IS" and -/// ALL WARRANTIES, EXPRESS OR IMPLIED are DISCLAIMED, INCLUDING BUT NOT LIMITED -/// TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY -/// RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A -/// PARTICULAR PURPOSE. -/// -#[derive(Debug)] -pub enum StandardError { - /// Invalid JSON was received by the server. - /// An error occurred on the server while parsing the JSON text. - ParseError, - /// The JSON sent is not a valid Request object. - InvalidRequest, - /// The method does not exist / is not available. - MethodNotFound, - /// Invalid method parameter(s). - InvalidParams, - /// Internal JSON-RPC error. - InternalError, -} - -#[derive(Clone, Debug, Deserialize, Serialize)] -/// A JSONRPC error object -pub struct RpcError { - /// The integer identifier of the error - pub code: i32, - /// A string describing the error - pub message: String, - /// Additional data specific to the error - pub data: Option>, -} - -/// Create a standard error responses -pub fn standard_error( - code: StandardError, - data: Option>, -) -> RpcError { - match code { - StandardError::ParseError => RpcError { - code: -32700, - message: "Parse error".to_string(), - data, - }, - StandardError::InvalidRequest => RpcError { - code: -32600, - message: "Invalid Request".to_string(), - data, - }, - StandardError::MethodNotFound => RpcError { - code: -32601, - message: "Method not found".to_string(), - data, - }, - StandardError::InvalidParams => RpcError { - code: -32602, - message: "Invalid params".to_string(), - data, - }, - StandardError::InternalError => RpcError { - code: -32603, - message: "Internal error".to_string(), - data, - }, - } -} - -/// Converts a Rust `Result` to a JSONRPC response object -pub fn result_to_response( - result: Result, - id: serde_json::Value, -) -> Response { - match result { - Ok(data) => Response { - result: Some( - serde_json::value::RawValue::from_string(serde_json::to_string(&data).unwrap()) - .unwrap(), - ), - error: None, - id, - jsonrpc: Some(String::from("2.0")), - }, - Err(err) => Response { - result: None, - error: Some(err), - id, - jsonrpc: Some(String::from("2.0")), - }, - } -} - -#[cfg(test)] -mod tests { - use super::StandardError::{ - InternalError, InvalidParams, InvalidRequest, MethodNotFound, ParseError, - }; - use super::{result_to_response, standard_error}; - use serde_json; - - #[test] - fn test_parse_error() { - let resp = result_to_response(Err(standard_error(ParseError, None)), From::from(1)); - assert!(resp.result.is_none()); - assert!(resp.error.is_some()); - assert_eq!(resp.id, serde_json::Value::from(1)); - assert_eq!(resp.error.unwrap().code, -32700); - } - - #[test] - fn test_invalid_request() { - let resp = result_to_response(Err(standard_error(InvalidRequest, None)), From::from(1)); - assert!(resp.result.is_none()); - assert!(resp.error.is_some()); - assert_eq!(resp.id, serde_json::Value::from(1)); - assert_eq!(resp.error.unwrap().code, -32600); - } - - #[test] - fn test_method_not_found() { - let resp = result_to_response(Err(standard_error(MethodNotFound, None)), From::from(1)); - assert!(resp.result.is_none()); - assert!(resp.error.is_some()); - assert_eq!(resp.id, serde_json::Value::from(1)); - assert_eq!(resp.error.unwrap().code, -32601); - } - - #[test] - fn test_invalid_params() { - let resp = result_to_response(Err(standard_error(InvalidParams, None)), From::from("123")); - assert!(resp.result.is_none()); - assert!(resp.error.is_some()); - assert_eq!(resp.id, serde_json::Value::from("123")); - assert_eq!(resp.error.unwrap().code, -32602); - } - - #[test] - fn test_internal_error() { - let resp = result_to_response(Err(standard_error(InternalError, None)), From::from(-1)); - assert!(resp.result.is_none()); - assert!(resp.error.is_some()); - assert_eq!(resp.id, serde_json::Value::from(-1)); - assert_eq!(resp.error.unwrap().code, -32603); - } -} diff --git a/src/json.rs b/src/json.rs new file mode 100644 index 00000000..706ab24a --- /dev/null +++ b/src/json.rs @@ -0,0 +1,323 @@ +//! Type definitions for the JSON objects described in the JSONRPC specification. + +use std::fmt; + +use erased_serde; +use serde::{Deserialize, Serialize}; +use serde_json::value::RawValue; + +use crate::Error; + +/// A JSONRPC request object. +#[derive(Serialize)] +pub struct Request<'a> { + /// The name of the RPC call. + pub method: &'a str, + /// Parameters to the RPC call. + pub params: &'a dyn erased_serde::Serialize, + /// Identifier for this Request, which should appear in the response. + pub id: serde_json::Value, + /// jsonrpc field, MUST be "2.0". + pub jsonrpc: Option<&'a str>, +} + +impl<'a> fmt::Debug for Request<'a> { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str(&serde_json::to_string(self).unwrap()) + //TODO(stevenroose) remove if unneeded + // let mut ret = Vec::::new(); + // let mut ser = serde_json::Serializer::new(&mut ret); + // erased_serde::serialize(&self, &mut ser).unwrap(); + // f.write_str(str::from_utf8(&ret).unwrap()) + } +} + +/// A JSONRPC response object. +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct Response { + /// A result if there is one, or [`None`]. + pub result: Option>, + /// An error if there is one, or [`None`]. + pub error: Option, + /// Identifier for this Request, which should match that of the request. + pub id: serde_json::Value, + /// jsonrpc field, MUST be "2.0". + pub jsonrpc: Option, +} + +impl Response { + /// Convert response into a raw result. + pub fn into_raw_result(self) -> Result, Error> { + if let Some(e) = self.error { + return Err(Error::Rpc(e)); + } + + if let Some(res) = self.result { + Ok(res) + } else { + Ok(RawValue::from_string(serde_json::to_string(&serde_json::Value::Null).unwrap()).unwrap()) + } + } + + /// Returns whether or not the `result` field is empty + pub fn is_none(&self) -> bool { + self.result.is_none() + } +} + +/// Standard error responses, as described at at +/// +/// +/// # Documentation Copyright +/// Copyright (C) 2007-2010 by the JSON-RPC Working Group +/// +/// This document and translations of it may be used to implement JSON-RPC, it +/// may be copied and furnished to others, and derivative works that comment +/// on or otherwise explain it or assist in its implementation may be prepared, +/// copied, published and distributed, in whole or in part, without restriction +/// of any kind, provided that the above copyright notice and this paragraph +/// are included on all such copies and derivative works. However, this document +/// itself may not be modified in any way. +/// +/// The limited permissions granted above are perpetual and will not be revoked. +/// +/// This document and the information contained herein is provided "AS IS" and +/// ALL WARRANTIES, EXPRESS OR IMPLIED are DISCLAIMED, INCLUDING BUT NOT LIMITED +/// TO ANY WARRANTY THAT THE USE OF THE INFORMATION HEREIN WILL NOT INFRINGE ANY +/// RIGHTS OR ANY IMPLIED WARRANTIES OF MERCHANTABILITY OR FITNESS FOR A +/// PARTICULAR PURPOSE. +/// +#[derive(Debug)] +pub enum StandardError { + /// Invalid JSON was received by the server. + /// An error occurred on the server while parsing the JSON text. + ParseError, + /// The JSON sent is not a valid Request object. + InvalidRequest, + /// The method does not exist / is not available. + MethodNotFound, + /// Invalid method parameter(s). + InvalidParams, + /// Internal JSON-RPC error. + InternalError, +} + +#[derive(Clone, Debug, Deserialize, Serialize)] +/// A JSONRPC error object +pub struct RpcError { + /// The integer identifier of the error + pub code: i32, + /// A string describing the error + pub message: String, + /// Additional data specific to the error + pub data: Option>, +} + +/// Create a standard error responses +pub fn standard_error( + code: StandardError, + data: Option>, +) -> RpcError { + match code { + StandardError::ParseError => RpcError { + code: -32700, + message: "Parse error".to_string(), + data, + }, + StandardError::InvalidRequest => RpcError { + code: -32600, + message: "Invalid Request".to_string(), + data, + }, + StandardError::MethodNotFound => RpcError { + code: -32601, + message: "Method not found".to_string(), + data, + }, + StandardError::InvalidParams => RpcError { + code: -32602, + message: "Invalid params".to_string(), + data, + }, + StandardError::InternalError => RpcError { + code: -32603, + message: "Internal error".to_string(), + data, + }, + } +} + +/// Converts a Rust `Result` to a JSONRPC response object +pub fn result_to_response( + result: Result, + id: serde_json::Value, +) -> Response { + match result { + Ok(data) => Response { + result: Some( + serde_json::value::RawValue::from_string(serde_json::to_string(&data).unwrap()) + .unwrap(), + ), + error: None, + id, + jsonrpc: Some(String::from("2.0")), + }, + Err(err) => Response { + result: None, + error: Some(err), + id, + jsonrpc: Some(String::from("2.0")), + }, + } +} + +#[cfg(test)] +mod tests { + use serde_json; + use serde_json::value::RawValue; + + use super::{Response, result_to_response, standard_error}; + use super::StandardError::{ + InternalError, InvalidParams, InvalidRequest, MethodNotFound, ParseError, + }; + + #[test] + fn response_is_none() { + let joanna = Response { + result: Some(RawValue::from_string(serde_json::to_string(&true).unwrap()).unwrap()), + error: None, + id: From::from(81), + jsonrpc: Some(String::from("2.0")), + }; + + let bill = Response { + result: None, + error: None, + id: From::from(66), + jsonrpc: Some(String::from("2.0")), + }; + + assert!(!joanna.is_none()); + assert!(bill.is_none()); + } + + #[test] + fn response_extract() { + let obj = vec!["Mary", "had", "a", "little", "lamb"]; + let response = Response { + result: Some(RawValue::from_string(serde_json::to_string(&obj).unwrap()).unwrap()), + error: None, + id: serde_json::Value::Null, + jsonrpc: Some(String::from("2.0")), + }; + let recovered1: Vec = response.result().unwrap(); + assert!(response.clone().check_error().is_ok()); + let recovered2: Vec = response.result().unwrap(); + assert_eq!(obj, recovered1); + assert_eq!(obj, recovered2); + } + + #[test] + fn null_result() { + let s = r#"{"result":null,"error":null,"id":"test"}"#; + let response: Response = serde_json::from_str(s).unwrap(); + let recovered1: Result<(), _> = response.result(); + let recovered2: Result<(), _> = response.result(); + assert!(recovered1.is_ok()); + assert!(recovered2.is_ok()); + + let recovered1: Result = response.result(); + let recovered2: Result = response.result(); + assert!(recovered1.is_err()); + assert!(recovered2.is_err()); + } + + #[test] + fn batch_response() { + // from the jsonrpc.org spec example + let s = r#"[ + {"jsonrpc": "2.0", "result": 7, "id": "1"}, + {"jsonrpc": "2.0", "result": 19, "id": "2"}, + {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}, + {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "5"}, + {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"} + ]"#; + let batch_response: Vec = serde_json::from_str(s).unwrap(); + assert_eq!(batch_response.len(), 5); + } + + #[test] + fn test_arg() { + macro_rules! test_arg { + ($val:expr, $t:ty) => {{ + let val1: $t = $val; + let arg = super::arg(val1.clone()); + let val2: $t = serde_json::from_str(arg.get()).expect(stringify!($val)); + assert_eq!(val1, val2, "failed test for {}", stringify!($val)); + }}; + } + + test_arg!(true, bool); + test_arg!(42, u8); + test_arg!(42, usize); + test_arg!(42, isize); + test_arg!(vec![42, 35], Vec); + test_arg!(String::from("test"), String); + + #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] + struct Test { + v: String, + } + test_arg!( + Test { + v: String::from("test"), + }, + Test + ); + } + + #[test] + fn test_parse_error() { + let resp = result_to_response(Err(standard_error(ParseError, None)), From::from(1)); + assert!(resp.result.is_none()); + assert!(resp.error.is_some()); + assert_eq!(resp.id, serde_json::Value::from(1)); + assert_eq!(resp.error.unwrap().code, -32700); + } + + #[test] + fn test_invalid_request() { + let resp = result_to_response(Err(standard_error(InvalidRequest, None)), From::from(1)); + assert!(resp.result.is_none()); + assert!(resp.error.is_some()); + assert_eq!(resp.id, serde_json::Value::from(1)); + assert_eq!(resp.error.unwrap().code, -32600); + } + + #[test] + fn test_method_not_found() { + let resp = result_to_response(Err(standard_error(MethodNotFound, None)), From::from(1)); + assert!(resp.result.is_none()); + assert!(resp.error.is_some()); + assert_eq!(resp.id, serde_json::Value::from(1)); + assert_eq!(resp.error.unwrap().code, -32601); + } + + #[test] + fn test_invalid_params() { + let resp = result_to_response(Err(standard_error(InvalidParams, None)), From::from("123")); + assert!(resp.result.is_none()); + assert!(resp.error.is_some()); + assert_eq!(resp.id, serde_json::Value::from("123")); + assert_eq!(resp.error.unwrap().code, -32602); + } + + #[test] + fn test_internal_error() { + let resp = result_to_response(Err(standard_error(InternalError, None)), From::from(-1)); + assert!(resp.result.is_none()); + assert!(resp.error.is_some()); + assert_eq!(resp.id, serde_json::Value::from(-1)); + assert_eq!(resp.error.unwrap().code, -32603); + } +} diff --git a/src/lib.rs b/src/lib.rs index 27b18b55..9e6c59cf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,16 +22,16 @@ // Coding conventions #![warn(missing_docs)] -use serde::{Deserialize, Serialize}; - extern crate serde; pub extern crate serde_json; +extern crate erased_serde; #[cfg(feature = "base64-compat")] pub extern crate base64; pub mod client; pub mod error; +pub mod json; mod util; #[cfg(feature = "simple_http")] @@ -44,8 +44,9 @@ pub mod simple_tcp; pub mod simple_uds; // Re-export error type -pub use crate::client::{Client, Transport}; +pub use crate::client::{Client, Request, SyncTransport, AsyncTransport}; pub use crate::error::Error; +pub use crate::json::{RpcError, StandardError}; use serde_json::value::RawValue; @@ -69,160 +70,3 @@ pub fn arg(arg: T) -> Box { }), } } - -#[derive(Debug, Clone, Serialize)] -/// A JSONRPC request object. -pub struct Request<'a> { - /// The name of the RPC call. - pub method: &'a str, - /// Parameters to the RPC call. - pub params: &'a [Box], - /// Identifier for this Request, which should appear in the response. - pub id: serde_json::Value, - /// jsonrpc field, MUST be "2.0". - pub jsonrpc: Option<&'a str>, -} - -#[derive(Debug, Clone, Deserialize, Serialize)] -/// A JSONRPC response object. -pub struct Response { - /// A result if there is one, or [`None`]. - pub result: Option>, - /// An error if there is one, or [`None`]. - pub error: Option, - /// Identifier for this Request, which should match that of the request. - pub id: serde_json::Value, - /// jsonrpc field, MUST be "2.0". - pub jsonrpc: Option, -} - -impl Response { - /// Extracts the result from a response. - pub fn result serde::de::Deserialize<'a>>(&self) -> Result { - if let Some(ref e) = self.error { - return Err(Error::Rpc(e.clone())); - } - - if let Some(ref res) = self.result { - serde_json::from_str(res.get()).map_err(Error::Json) - } else { - serde_json::from_value(serde_json::Value::Null).map_err(Error::Json) - } - } - - /// Returns the RPC error, if there was one, but does not check the result. - pub fn check_error(self) -> Result<(), Error> { - if let Some(e) = self.error { - Err(Error::Rpc(e)) - } else { - Ok(()) - } - } - - /// Returns whether or not the `result` field is empty. - pub fn is_none(&self) -> bool { - self.result.is_none() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - use serde_json::value::RawValue; - - #[test] - fn response_is_none() { - let joanna = Response { - result: Some(RawValue::from_string(serde_json::to_string(&true).unwrap()).unwrap()), - error: None, - id: From::from(81), - jsonrpc: Some(String::from("2.0")), - }; - - let bill = Response { - result: None, - error: None, - id: From::from(66), - jsonrpc: Some(String::from("2.0")), - }; - - assert!(!joanna.is_none()); - assert!(bill.is_none()); - } - - #[test] - fn response_extract() { - let obj = vec!["Mary", "had", "a", "little", "lamb"]; - let response = Response { - result: Some(RawValue::from_string(serde_json::to_string(&obj).unwrap()).unwrap()), - error: None, - id: serde_json::Value::Null, - jsonrpc: Some(String::from("2.0")), - }; - let recovered1: Vec = response.result().unwrap(); - assert!(response.clone().check_error().is_ok()); - let recovered2: Vec = response.result().unwrap(); - assert_eq!(obj, recovered1); - assert_eq!(obj, recovered2); - } - - #[test] - fn null_result() { - let s = r#"{"result":null,"error":null,"id":"test"}"#; - let response: Response = serde_json::from_str(s).unwrap(); - let recovered1: Result<(), _> = response.result(); - let recovered2: Result<(), _> = response.result(); - assert!(recovered1.is_ok()); - assert!(recovered2.is_ok()); - - let recovered1: Result = response.result(); - let recovered2: Result = response.result(); - assert!(recovered1.is_err()); - assert!(recovered2.is_err()); - } - - #[test] - fn batch_response() { - // from the jsonrpc.org spec example - let s = r#"[ - {"jsonrpc": "2.0", "result": 7, "id": "1"}, - {"jsonrpc": "2.0", "result": 19, "id": "2"}, - {"jsonrpc": "2.0", "error": {"code": -32600, "message": "Invalid Request"}, "id": null}, - {"jsonrpc": "2.0", "error": {"code": -32601, "message": "Method not found"}, "id": "5"}, - {"jsonrpc": "2.0", "result": ["hello", 5], "id": "9"} - ]"#; - let batch_response: Vec = serde_json::from_str(s).unwrap(); - assert_eq!(batch_response.len(), 5); - } - - #[test] - fn test_arg() { - macro_rules! test_arg { - ($val:expr, $t:ty) => {{ - let val1: $t = $val; - let arg = super::arg(val1.clone()); - let val2: $t = serde_json::from_str(arg.get()).expect(stringify!($val)); - assert_eq!(val1, val2, "failed test for {}", stringify!($val)); - }}; - } - - test_arg!(true, bool); - test_arg!(42, u8); - test_arg!(42, usize); - test_arg!(42, isize); - test_arg!(vec![42, 35], Vec); - test_arg!(String::from("test"), String); - - #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)] - struct Test { - v: String, - } - test_arg!( - Test { - v: String::from("test"), - }, - Test - ); - } -} diff --git a/src/simple_http.rs b/src/simple_http.rs index bcd6119e..5add58a8 100644 --- a/src/simple_http.rs +++ b/src/simple_http.rs @@ -11,14 +11,14 @@ use std::net::TcpStream; use std::net::{SocketAddr, ToSocketAddrs}; use std::sync::{Arc, Mutex, MutexGuard}; use std::time::Duration; -use std::{error, fmt, io, net, num}; +use std::{fmt, io, net, num}; use base64; use serde; use serde_json; -use crate::client::Transport; -use crate::{Request, Response}; +use crate::json; +use crate::{Client, SyncTransport}; #[cfg(fuzzing)] /// Global mutex used by the fuzzing harness to inject data into the read @@ -56,7 +56,6 @@ mod impls { } } - /// The default TCP port to use for connections. /// Set to 8332, the default RPC port for bitcoind. pub const DEFAULT_PORT: u16 = 8332; @@ -408,8 +407,8 @@ impl fmt::Display for Error { } } -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { use self::Error::*; match *self { @@ -510,18 +509,14 @@ fn check_url(url: &str) -> Result<(SocketAddr, String), Error> { } } -impl Transport for SimpleHttpTransport { - fn send_request(&self, req: Request) -> Result { +impl SyncTransport for SimpleHttpTransport { + fn send_request(&self, req: &json::Request) -> Result { Ok(self.request(req)?) } - fn send_batch(&self, reqs: &[Request]) -> Result, crate::Error> { + fn send_batch(&self, reqs: &[json::Request]) -> Result, crate::Error> { Ok(self.request(reqs)?) } - - fn fmt_target(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "http://{}:{}{}", self.addr.ip(), self.addr.port(), self.path) - } } /// Builder for simple bitcoind [`SimpleHttpTransport`]. @@ -550,8 +545,8 @@ impl Builder { Ok(self) } - /// Adds authentication information to the transport. - pub fn auth>(mut self, user: S, pass: Option) -> Self { + /// Add authentication information to the transport. + pub fn auth(mut self, user: impl AsRef, pass: Option>) -> Self { let mut auth = user.as_ref().to_owned(); auth.push(':'); if let Some(ref pass) = pass { @@ -595,29 +590,32 @@ impl Default for Builder { } } -impl crate::Client { - /// Creates a new JSON-RPC client using a bare-minimum HTTP transport. - pub fn simple_http( +/// A client using the [SimpleHttpTransport] transport. +pub type SimpleHttpClient = Client; + +impl Client { + /// Create a new JSON-RPC client using a bare-minimum HTTP transport. + pub fn with_simple_http( url: &str, user: Option, pass: Option, - ) -> Result { - let mut builder = Builder::new().url(url)?; + ) -> Result, Error> { + let mut builder = Builder::new().url(&url)?; if let Some(user) = user { builder = builder.auth(user, pass); } - Ok(crate::Client::with_transport(builder.build())) + Ok(Client::new(builder.build())) } #[cfg(feature = "proxy")] /// Creates a new JSON_RPC client using a HTTP-Socks5 proxy transport. - pub fn http_proxy( + pub fn with_simple_http_and_http_proxy( url: &str, user: Option, pass: Option, proxy_addr: &str, proxy_auth: Option<(&str, &str)>, - ) -> Result { + ) -> Result, Error> { let mut builder = Builder::new().url(url)?; if let Some(user) = user { builder = builder.auth(user, pass); @@ -626,8 +624,7 @@ impl crate::Client { if let Some((user, pass)) = proxy_auth { builder = builder.proxy_auth(user, pass); } - let tp = builder.build(); - Ok(crate::Client::with_transport(tp)) + Ok(Client::new(builder.build())) } } diff --git a/src/simple_tcp.rs b/src/simple_tcp.rs index 3b11ad50..e351b447 100644 --- a/src/simple_tcp.rs +++ b/src/simple_tcp.rs @@ -2,13 +2,14 @@ //! it does not handle TCP over Unix Domain Sockets, see `simple_uds` for this. //! -use std::{error, fmt, io, net, time}; +use std::{fmt, io, net}; +use std::time::Duration; use serde; use serde_json; -use crate::client::Transport; -use crate::{Request, Response}; +use crate::client::{Client, SyncTransport}; +use crate::json; /// Error that can occur while using the TCP transport. #[derive(Debug)] @@ -31,8 +32,8 @@ impl fmt::Display for Error { } } -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { use self::Error::*; match *self { @@ -66,17 +67,25 @@ impl From for crate::Error { /// Simple synchronous TCP transport. #[derive(Debug, Clone)] -pub struct TcpTransport { - /// The internet socket address to connect to. +pub struct SimpleTcpTransport { + /// The internet socket address to connect to pub addr: net::SocketAddr, - /// The read and write timeout to use for this connection. - pub timeout: Option, + /// The read and write timeout to use for this connection + pub timeout: Option, } -impl TcpTransport { - /// Creates a new TcpTransport without timeouts. - pub fn new(addr: net::SocketAddr) -> TcpTransport { - TcpTransport { +impl SimpleTcpTransport { + /// Create a new [SimpleTcpTransport] without timeouts + pub fn with_timeout(addr: net::SocketAddr, timeout: Duration) -> SimpleTcpTransport { + SimpleTcpTransport { + addr, + timeout: Some(timeout), + } + } + + /// Create a new [SimpleTcpTransport] without timeouts + pub fn new(addr: net::SocketAddr) -> SimpleTcpTransport { + SimpleTcpTransport { addr, timeout: None, } @@ -101,17 +110,25 @@ impl TcpTransport { } } -impl Transport for TcpTransport { - fn send_request(&self, req: Request) -> Result { +impl SyncTransport for SimpleTcpTransport { + fn send_request(&self, req: &json::Request) -> Result { Ok(self.request(req)?) } - fn send_batch(&self, reqs: &[Request]) -> Result, crate::Error> { + fn send_batch(&self, reqs: &[json::Request]) -> Result, crate::Error> { Ok(self.request(reqs)?) } +} + +/// A client using the [SimpleTcpTransport] transport. +pub type SimpleTcpClient = Client; - fn fmt_target(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.addr) +impl Client { + /// Create a new JSON-RPC client using a bare-minimum TCP transport. + pub fn with_simple_tcp( + socket_addr: net::SocketAddr + ) -> Client { + Client::new(SimpleTcpTransport::new(socket_addr)) } } @@ -132,14 +149,14 @@ mod tests { net::SocketAddrV4::new(net::Ipv4Addr::new(127, 0, 0, 1), 0).into(); let server = net::TcpListener::bind(addr).unwrap(); let addr = server.local_addr().unwrap(); - let dummy_req = Request { + let dummy_req = json::Request { method: "arandommethod", params: &[], id: serde_json::Value::Number(4242242.into()), jsonrpc: Some("2.0"), }; let dummy_req_ser = serde_json::to_vec(&dummy_req).unwrap(); - let dummy_resp = Response { + let dummy_resp = json::Response { result: None, error: None, id: serde_json::Value::Number(4242242.into()), @@ -148,9 +165,9 @@ mod tests { let dummy_resp_ser = serde_json::to_vec(&dummy_resp).unwrap(); let client_thread = thread::spawn(move || { - let transport = TcpTransport { + let transport = SimpleTcpTransport { addr, - timeout: Some(time::Duration::from_secs(5)), + timeout: Some(Duration::from_secs(5)), }; let client = Client::with_transport(transport); @@ -158,7 +175,7 @@ mod tests { }); let (mut stream, _) = server.accept().unwrap(); - stream.set_read_timeout(Some(time::Duration::from_secs(5))).unwrap(); + stream.set_read_timeout(Some(Duration::from_secs(5))).unwrap(); let mut recv_req = vec![0; dummy_req_ser.len()]; let mut read = 0; while read < dummy_req_ser.len() { diff --git a/src/simple_uds.rs b/src/simple_uds.rs index f65be4ea..9bbf2f33 100644 --- a/src/simple_uds.rs +++ b/src/simple_uds.rs @@ -1,14 +1,15 @@ -//! This module implements a synchronous transport over a raw TcpListener. -//! +//! This module implements a synchronous transport over a raw Unix socket. +use std::{fmt, io}; use std::os::unix::net::UnixStream; -use std::{error, fmt, io, path, time}; +use std::path::{Path, PathBuf}; +use std::time::Duration; use serde; use serde_json; -use crate::client::Transport; -use crate::{Request, Response}; +use crate::client::{Client, SyncTransport}; +use crate::json; /// Error that can occur while using the UDS transport. #[derive(Debug)] @@ -31,8 +32,8 @@ impl fmt::Display for Error { } } -impl error::Error for Error { - fn source(&self) -> Option<&(dyn error::Error + 'static)> { +impl std::error::Error for Error { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { use self::Error::*; match *self { @@ -55,33 +56,41 @@ impl From for Error { } } -impl From for crate::error::Error { - fn from(e: Error) -> crate::error::Error { +impl From for crate::Error { + fn from(e: Error) -> crate::Error { match e { - Error::Json(e) => crate::error::Error::Json(e), - e => crate::error::Error::Transport(Box::new(e)), + Error::Json(e) => crate::Error::Json(e), + e => crate::Error::Transport(Box::new(e)), } } } /// Simple synchronous UDS transport. #[derive(Debug, Clone)] -pub struct UdsTransport { - /// The path to the Unix Domain Socket. - pub sockpath: path::PathBuf, - /// The read and write timeout to use. - pub timeout: Option, +pub struct SimpleUdsTransport { + /// The path to the Unix Domain Socket + pub sockpath: PathBuf, + /// The read and write timeout to use + pub timeout: Option, } -impl UdsTransport { - /// Creates a new [`UdsTransport`] without timeouts to use. - pub fn new>(sockpath: P) -> UdsTransport { - UdsTransport { +impl SimpleUdsTransport { + /// Create a new [SimpleUdsTransport] without timeouts to use + pub fn new(sockpath: impl AsRef) -> SimpleUdsTransport { + SimpleUdsTransport { sockpath: sockpath.as_ref().to_path_buf(), timeout: None, } } + /// Create a new [SimpleUdsTransport] without timeouts to use + pub fn with_timeout(sockpath: impl AsRef, timeout: Duration) -> SimpleUdsTransport { + SimpleUdsTransport { + sockpath: sockpath.as_ref().to_path_buf(), + timeout: Some(timeout), + } + } + fn request(&self, req: impl serde::Serialize) -> Result where R: for<'a> serde::de::Deserialize<'a>, @@ -101,17 +110,25 @@ impl UdsTransport { } } -impl Transport for UdsTransport { - fn send_request(&self, req: Request) -> Result { +impl SyncTransport for SimpleUdsTransport { + fn send_request(&self, req: &json::Request) -> Result { Ok(self.request(req)?) } - fn send_batch(&self, reqs: &[Request]) -> Result, crate::error::Error> { + fn send_batch(&self, reqs: &[json::Request]) -> Result, crate::Error> { Ok(self.request(reqs)?) } +} + +/// A client using the [SimpleTcpTransport] transport. +pub type SimpleUdsClient = Client; - fn fmt_target(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.sockpath.to_string_lossy()) +impl Client { + /// Create a new JSON-RPC client using a bare-minimum UDS transport. + pub fn with_simple_uds>( + socket_path: P, + ) -> Client { + Client::new(SimpleUdsTransport::new(socket_path)) } } @@ -130,31 +147,31 @@ mod tests { // Test a dummy request / response over an UDS #[test] fn sanity_check_uds_transport() { - let socket_path: path::PathBuf = format!("uds_scratch_{}.socket", process::id()).into(); + let socket_path = PathBuf::from(format!("uds_scratch_{}.socket", process::id())); // Any leftover? fs::remove_file(&socket_path).unwrap_or(()); let server = UnixListener::bind(&socket_path).unwrap(); - let dummy_req = Request { + let dummy_req = json::Request { method: "getinfo", params: &[], - id: serde_json::Value::Number(111.into()), - jsonrpc: Some("2.0"), + id: serde_json::Value::Number(111usize.into()), + jsonrpc: Some("2.0".into()), }; let dummy_req_ser = serde_json::to_vec(&dummy_req).unwrap(); - let dummy_resp = Response { + let dummy_resp = json::Response { result: None, error: None, - id: serde_json::Value::Number(111.into()), + id: serde_json::Value::Number(111usize.into()), jsonrpc: Some("2.0".into()), }; let dummy_resp_ser = serde_json::to_vec(&dummy_resp).unwrap(); let cli_socket_path = socket_path.clone(); let client_thread = thread::spawn(move || { - let transport = UdsTransport { + let transport = SimpleUdsTransport { sockpath: cli_socket_path, - timeout: Some(time::Duration::from_secs(5)), + timeout: Some(Duration::from_secs(5)), }; let client = Client::with_transport(transport); @@ -162,7 +179,7 @@ mod tests { }); let (mut stream, _) = server.accept().unwrap(); - stream.set_read_timeout(Some(time::Duration::from_secs(5))).unwrap(); + stream.set_read_timeout(Some(Duration::from_secs(5))).unwrap(); let mut recv_req = vec![0; dummy_req_ser.len()]; let mut read = 0; while read < dummy_req_ser.len() {