casper_json_rpc/lib.rs
1//! # casper-json-rpc
2//!
3//! A library suitable for use as the framework for a JSON-RPC server.
4//!
5//! # Usage
6//!
7//! Normally usage will involve two steps:
8//! * construct a set of request handlers using a [`RequestHandlersBuilder`]
9//! * call [`casper_json_rpc::route`](route) to construct a boxed warp filter ready to be passed
10//! to [`warp::service`](https://docs.rs/warp/latest/warp/fn.service.html) for example
11//!
12//! # Example
13//!
14//! ```no_run
15//! use casper_json_rpc::{Error, Params, RequestHandlersBuilder};
16//! use std::{convert::Infallible, sync::Arc};
17//!
18//! # #[allow(unused)]
19//! async fn get(params: Option<Params>) -> Result<String, Error> {
20//! // * parse params or return `ReservedErrorCode::InvalidParams` error
21//! // * handle request and return result
22//! Ok("got it".to_string())
23//! }
24//!
25//! # #[allow(unused)]
26//! async fn put(params: Option<Params>, other_input: &str) -> Result<String, Error> {
27//! Ok(other_input.to_string())
28//! }
29//!
30//! #[tokio::main]
31//! async fn main() {
32//! // Register handlers for methods "get" and "put".
33//! let mut handlers = RequestHandlersBuilder::new();
34//! handlers.register_handler("get", Arc::new(get));
35//! let put_handler = move |params| async move { put(params, "other input").await };
36//! handlers.register_handler("put", Arc::new(put_handler));
37//! let handlers = handlers.build();
38//!
39//! // Get the new route.
40//! let path = "rpc";
41//! let max_body_bytes = 1024;
42//! let allow_unknown_fields = false;
43//! let route = casper_json_rpc::route(path, max_body_bytes, handlers, allow_unknown_fields);
44//!
45//! // Convert it into a `Service` and run it.
46//! let make_svc = hyper::service::make_service_fn(move |_| {
47//! let svc = warp::service(route.clone());
48//! async move { Ok::<_, Infallible>(svc.clone()) }
49//! });
50//!
51//! hyper::Server::bind(&([127, 0, 0, 1], 3030).into())
52//! .serve(make_svc)
53//! .await
54//! .unwrap();
55//! }
56//! ```
57//!
58//! # Errors
59//!
60//! To return a JSON-RPC response indicating an error, use [`Error::new`]. Most error conditions
61//! which require returning a reserved error are already handled in the provided warp filters. The
62//! only exception is [`ReservedErrorCode::InvalidParams`] which should be returned by any RPC
63//! handler which deems the provided `params: Option<Params>` to be invalid for any reason.
64//!
65//! Generally a set of custom error codes should be provided. These should all implement
66//! [`ErrorCodeT`].
67
68#![doc(html_root_url = "https://docs.rs/casper-json-rpc/1.1.0")]
69#![doc(
70 html_favicon_url = "https://raw.githubusercontent.com/casper-network/casper-node/master/images/CasperLabs_Logo_Favicon_RGB_50px.png",
71 html_logo_url = "https://raw.githubusercontent.com/casper-network/casper-node/master/images/CasperLabs_Logo_Symbol_RGB.png",
72 test(attr(deny(warnings)))
73)]
74#![warn(
75 missing_docs,
76 trivial_casts,
77 trivial_numeric_casts,
78 unused_qualifications
79)]
80
81mod error;
82pub mod filters;
83mod rejections;
84mod request;
85mod request_handlers;
86mod response;
87
88use http::{header::CONTENT_TYPE, Method};
89use warp::{filters::BoxedFilter, Filter, Reply};
90
91pub use error::{Error, ErrorCodeT, ReservedErrorCode};
92pub use request::Params;
93pub use request_handlers::{RequestHandlers, RequestHandlersBuilder};
94pub use response::Response;
95
96const JSON_RPC_VERSION: &str = "2.0";
97
98/// Specifies the CORS origin
99pub enum CorsOrigin {
100 /// Any (*) origin is allowed.
101 Any,
102 /// Only the specified origin is allowed.
103 Specified(String),
104}
105
106/// Constructs a set of warp filters suitable for use in a JSON-RPC server.
107///
108/// `path` specifies the exact HTTP path for JSON-RPC requests, e.g. "rpc" will match requests on
109/// exactly "/rpc", and not "/rpc/other".
110///
111/// `max_body_bytes` sets an upper limit for the number of bytes in the HTTP request body. For
112/// further details, see
113/// [`warp::filters::body::content_length_limit`](https://docs.rs/warp/latest/warp/filters/body/fn.content_length_limit.html).
114///
115/// `handlers` is the map of functions to which incoming requests will be dispatched. These are
116/// keyed by the JSON-RPC request's "method".
117///
118/// If `allow_unknown_fields` is `false`, requests with unknown fields will cause the server to
119/// respond with an error.
120///
121/// For further details, see the docs for the [`filters`] functions.
122pub fn route<P: AsRef<str>>(
123 path: P,
124 max_body_bytes: u32,
125 handlers: RequestHandlers,
126 allow_unknown_fields: bool,
127) -> BoxedFilter<(impl Reply,)> {
128 filters::base_filter(path, max_body_bytes)
129 .and(filters::main_filter(handlers, allow_unknown_fields))
130 .recover(filters::handle_rejection)
131 .boxed()
132}
133
134/// Constructs a set of warp filters suitable for use in a JSON-RPC server.
135///
136/// `path` specifies the exact HTTP path for JSON-RPC requests, e.g. "rpc" will match requests on
137/// exactly "/rpc", and not "/rpc/other".
138///
139/// `max_body_bytes` sets an upper limit for the number of bytes in the HTTP request body. For
140/// further details, see
141/// [`warp::filters::body::content_length_limit`](https://docs.rs/warp/latest/warp/filters/body/fn.content_length_limit.html).
142///
143/// `handlers` is the map of functions to which incoming requests will be dispatched. These are
144/// keyed by the JSON-RPC request's "method".
145///
146/// If `allow_unknown_fields` is `false`, requests with unknown fields will cause the server to
147/// respond with an error.
148///
149/// Note that this is a convenience function combining the lower-level functions in [`filters`]
150/// along with [a warp CORS filter](https://docs.rs/warp/latest/warp/filters/cors/index.html) which
151/// * allows any origin or specified origin
152/// * allows "content-type" as a header
153/// * allows the method "POST"
154///
155/// For further details, see the docs for the [`filters`] functions.
156pub fn route_with_cors<P: AsRef<str>>(
157 path: P,
158 max_body_bytes: u32,
159 handlers: RequestHandlers,
160 allow_unknown_fields: bool,
161 cors_header: &CorsOrigin,
162) -> BoxedFilter<(impl Reply,)> {
163 filters::base_filter(path, max_body_bytes)
164 .and(filters::main_filter(handlers, allow_unknown_fields))
165 .recover(filters::handle_rejection)
166 .with(match cors_header {
167 CorsOrigin::Any => warp::cors()
168 .allow_any_origin()
169 .allow_header(CONTENT_TYPE)
170 .allow_method(Method::POST),
171 CorsOrigin::Specified(origin) => warp::cors()
172 .allow_origin(origin.as_str())
173 .allow_header(CONTENT_TYPE)
174 .allow_method(Method::POST),
175 })
176 .boxed()
177}