quic_rpc/lib.rs
1//! A streaming rpc system for transports that support multiple bidirectional
2//! streams, such as QUIC and HTTP2.
3//!
4//! A lightweight memory transport is provided for cases where you want have
5//! multiple cleanly separated substreams in the same process.
6//!
7//! For supported transports, see the [transport] module.
8//!
9//! # Motivation
10//!
11//! See the [README](https://github.com/n0-computer/quic-rpc/blob/main/README.md)
12//!
13//! # Example
14//! ```
15//! # async fn example() -> anyhow::Result<()> {
16//! use derive_more::{From, TryInto};
17//! use quic_rpc::{message::RpcMsg, RpcClient, RpcServer, Service};
18//! use serde::{Deserialize, Serialize};
19//!
20//! // Define your messages
21//! #[derive(Debug, Serialize, Deserialize)]
22//! struct Ping;
23//!
24//! #[derive(Debug, Serialize, Deserialize)]
25//! struct Pong;
26//!
27//! // Define your RPC service and its request/response types
28//! #[derive(Debug, Clone)]
29//! struct PingService;
30//!
31//! #[derive(Debug, Serialize, Deserialize, From, TryInto)]
32//! enum PingRequest {
33//! Ping(Ping),
34//! }
35//!
36//! #[derive(Debug, Serialize, Deserialize, From, TryInto)]
37//! enum PingResponse {
38//! Pong(Pong),
39//! }
40//!
41//! impl Service for PingService {
42//! type Req = PingRequest;
43//! type Res = PingResponse;
44//! }
45//!
46//! // Define interaction patterns for each request type
47//! impl RpcMsg<PingService> for Ping {
48//! type Response = Pong;
49//! }
50//!
51//! // create a transport channel, here a memory channel for testing
52//! let (server, client) = quic_rpc::transport::flume::channel(1);
53//!
54//! // client side
55//! // create the rpc client given the channel and the service type
56//! let mut client = RpcClient::<PingService, _>::new(client);
57//!
58//! // call the service
59//! let res = client.rpc(Ping).await?;
60//!
61//! // server side
62//! // create the rpc server given the channel and the service type
63//! let mut server = RpcServer::<PingService, _>::new(server);
64//!
65//! let handler = Handler;
66//! loop {
67//! // accept connections
68//! let (msg, chan) = server.accept().await?.read_first().await?;
69//! // dispatch the message to the appropriate handler
70//! match msg {
71//! PingRequest::Ping(ping) => chan.rpc(ping, handler, Handler::ping).await?,
72//! }
73//! }
74//!
75//! // the handler. For a more complex example, this would contain any state
76//! // needed to handle the request.
77//! #[derive(Debug, Clone, Copy)]
78//! struct Handler;
79//!
80//! impl Handler {
81//! // the handle fn for a Ping request.
82//!
83//! // The return type is the response type for the service.
84//! // Note that this must take self by value, not by reference.
85//! async fn ping(self, _req: Ping) -> Pong {
86//! Pong
87//! }
88//! }
89//! # Ok(())
90//! # }
91//! ```
92//!
93//! # Features
94#![doc = document_features::document_features!()]
95#![deny(missing_docs)]
96#![deny(rustdoc::broken_intra_doc_links)]
97#![cfg_attr(quicrpc_docsrs, feature(doc_cfg))]
98use std::fmt::{Debug, Display};
99
100use serde::{de::DeserializeOwned, Serialize};
101pub mod client;
102pub mod message;
103pub mod server;
104pub mod transport;
105pub use client::RpcClient;
106pub use server::RpcServer;
107#[cfg(feature = "macros")]
108#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "macros")))]
109mod macros;
110
111pub mod pattern;
112
113/// Requirements for a RPC message
114///
115/// Even when just using the mem transport, we require messages to be Serializable and Deserializable.
116/// Likewise, even when using the quinn transport, we require messages to be Send.
117///
118/// This does not seem like a big restriction. If you want a pure memory channel without the possibility
119/// to also use the quinn transport, you might want to use a mpsc channel directly.
120pub trait RpcMessage: Debug + Serialize + DeserializeOwned + Send + Sync + Unpin + 'static {}
121
122impl<T> RpcMessage for T where
123 T: Debug + Serialize + DeserializeOwned + Send + Sync + Unpin + 'static
124{
125}
126
127/// Requirements for an internal error
128///
129/// All errors have to be Send, Sync and 'static so they can be sent across threads.
130/// They also have to be Debug and Display so they can be logged.
131///
132/// We don't require them to implement [std::error::Error] so we can use
133/// anyhow::Error as an error type.
134///
135/// Instead we require them to implement `Into<anyhow::Error>`, which is available
136/// both for any type that implements [std::error::Error] and anyhow itself.
137pub trait RpcError: Debug + Display + Into<anyhow::Error> + Send + Sync + Unpin + 'static {}
138
139impl<T> RpcError for T where T: Debug + Display + Into<anyhow::Error> + Send + Sync + Unpin + 'static
140{}
141
142/// A service
143///
144/// A service has request and response message types. These types have to be the
145/// union of all possible request and response types for all interactions with
146/// the service.
147///
148/// Usually you will define an enum for the request and response
149/// type, and use the [derive_more](https://crates.io/crates/derive_more) crate to
150/// define the conversions between the enum and the actual request and response types.
151///
152/// To make a message type usable as a request for a service, implement [message::Msg]
153/// for it. This is how you define the interaction patterns for each request type.
154///
155/// Depending on the interaction type, you might need to implement traits that further
156/// define details of the interaction.
157///
158/// A message type can be used for multiple services. E.g. you might have a
159/// Status request that is understood by multiple services and returns a
160/// standard status response.
161pub trait Service: Send + Sync + Debug + Clone + 'static {
162 /// Type of request messages
163 type Req: RpcMessage;
164 /// Type of response messages
165 type Res: RpcMessage;
166}
167
168/// A connector to a specific service
169///
170/// This is just a trait alias for a [`transport::Connector`] with the right types. It is used
171/// to make it easier to specify the bounds of a connector that matches a specific
172/// service.
173pub trait Connector<S: Service>: transport::Connector<In = S::Res, Out = S::Req> {}
174
175impl<T: transport::Connector<In = S::Res, Out = S::Req>, S: Service> Connector<S> for T {}
176
177/// A listener for a specific service
178///
179/// This is just a trait alias for a [`transport::Listener`] with the right types. It is used
180/// to make it easier to specify the bounds of a listener that matches a specific
181/// service.
182pub trait Listener<S: Service>: transport::Listener<In = S::Req, Out = S::Res> {}
183
184impl<T: transport::Listener<In = S::Req, Out = S::Res>, S: Service> Listener<S> for T {}
185
186#[cfg(feature = "flume-transport")]
187#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "flume-transport")))]
188/// Create a pair of [`RpcServer`] and [`RpcClient`] for the given [`Service`] type using a flume channel
189pub fn flume_channel<S: Service>(
190 size: usize,
191) -> (
192 RpcServer<S, server::FlumeListener<S>>,
193 RpcClient<S, client::FlumeConnector<S>>,
194) {
195 let (listener, connector) = transport::flume::channel(size);
196 (RpcServer::new(listener), RpcClient::new(connector))
197}
198
199#[cfg(feature = "test-utils")]
200#[cfg_attr(quicrpc_docsrs, doc(cfg(feature = "test-utils")))]
201/// Create a pair of [`RpcServer`] and [`RpcClient`] for the given [`Service`] type using a quinn channel
202///
203/// This is using a network connection using the local network. It is useful for testing remote services
204/// in a more realistic way than the memory transport.
205#[allow(clippy::type_complexity)]
206pub fn quinn_channel<S: Service>() -> anyhow::Result<(
207 RpcServer<S, server::QuinnListener<S>>,
208 RpcClient<S, client::QuinnConnector<S>>,
209)> {
210 let bind_addr: std::net::SocketAddr = ([0, 0, 0, 0], 0).into();
211 let (server_endpoint, cert_der) = transport::quinn::make_server_endpoint(bind_addr)?;
212 let addr = server_endpoint.local_addr()?;
213 let server = server::QuinnListener::<S>::new(server_endpoint)?;
214 let server = RpcServer::new(server);
215 let client_endpoint = transport::quinn::make_client_endpoint(bind_addr, &[&cert_der])?;
216 let client = client::QuinnConnector::<S>::new(client_endpoint, addr, "localhost".into());
217 let client = RpcClient::new(client);
218 Ok((server, client))
219}