Skip to main content

dig_rpc/
lib.rs

1//! # dig-rpc
2//!
3//! An axum-based JSON-RPC **server framework** for a DIG node. It serves the
4//! canonical [`dig-rpc-protocol`](dig_rpc_protocol) interface over the three DIG
5//! transport surfaces and owns everything transport-shaped so a node doesn't
6//! have to:
7//!
8//! - **mTLS** peer surface, **HTTPS** public-read surface, **loopback** control
9//!   surface — one [`RpcServer`] per surface, over the same handler;
10//! - the JSON-RPC 2.0 envelope + the uniform error envelope;
11//! - the surface/tier **allowlist boundary** ([`dispatch`](mod@dispatch)) — a
12//!   control method is unreachable off loopback, a non-allowlisted method is
13//!   unreachable over the peer surface;
14//! - `rpc.discover` served from the generated OpenRPC document;
15//! - per-(peer, tier) **rate limiting**;
16//! - **graceful shutdown** driven by any future.
17//!
18//! The node supplies the *semantics* through one small trait,
19//! [`RpcHandler`] — so this crate depends ONLY on [`dig_rpc_protocol`], never on a
20//! node or service crate. (The previous design's `dig-service` dependency is
21//! gone: the shutdown signal is a plain future and dispatch is a trait, not an
22//! external `RpcApi`.)
23//!
24//! ## Architecture
25//!
26//! ```text
27//!   POST /  (one surface: Loopback | PublicRead | Peer)
28//!      │
29//!      ▼  axum handler
30//!   ┌────────────────────────────────────────────────┐
31//!   │ rate limit  — per (peer, tier) token bucket      │
32//!   │ dispatch    — resolve method (dig-rpc-protocol)      │
33//!   │             — surface/tier allowlist boundary     │
34//!   │             — rpc.discover from OpenRPC generator │
35//!   │             — RpcHandler::handle (node semantics) │
36//!   │ envelope    — JsonRpcResponse { result | error }  │
37//!   └────────────────────────────────────────────────┘
38//! ```
39//!
40//! ## Minimal handler
41//!
42//! ```
43//! use dig_rpc::{RpcHandler};
44//! use dig_rpc_protocol::{Method, RpcError, ErrorCode};
45//! use serde_json::{json, Value};
46//!
47//! struct MyNode;
48//!
49//! #[async_trait::async_trait]
50//! impl RpcHandler for MyNode {
51//!     async fn handle(&self, method: Method, _params: Value) -> Result<Value, RpcError> {
52//!         match method {
53//!             Method::Health => Ok(json!({ "status": "ok" })),
54//!             other => Err(RpcError::of(
55//!                 ErrorCode::MethodNotFound,
56//!                 format!("{} not served", other.name()),
57//!             )),
58//!         }
59//!     }
60//! }
61//! ```
62
63#![forbid(unsafe_code)]
64#![warn(missing_docs)]
65
66pub mod dispatch;
67pub mod error;
68pub mod handler;
69pub mod middleware;
70pub mod server;
71pub mod tls;
72
73pub use dispatch::{dispatch, parse_error_response, SharedHandler, Surface};
74pub use error::RpcServerError;
75pub use handler::RpcHandler;
76pub use middleware::{RateLimitConfig, RateLimitState};
77pub use server::{RpcServer, RpcServerMode};
78pub use tls::{InternalCertPaths, PublicCertPaths, TlsConfig};
79
80// Re-export the wire contract for ergonomic downstream use.
81pub use dig_rpc_protocol::{
82    self, envelope, ErrorCode, ErrorOrigin, JsonRpcRequest, JsonRpcResponse, Method, RpcError, Tier,
83};
84
85#[cfg(test)]
86mod conformance {
87    //! Guards the single-source-of-truth boundary: this crate must never grow
88    //! a local shadow of the wire contract. If a re-export here stops
89    //! resolving to `dig_rpc_protocol`'s type, downstream consumers (dig-node,
90    //! the JS binding's Rust-side name-agreement test) silently diverge from
91    //! the canonical crate — so pin the identity at compile time.
92    use crate::{ErrorCode, ErrorOrigin, JsonRpcRequest, JsonRpcResponse, Method, RpcError, Tier};
93
94    fn assert_same_type<T>(_: T) {}
95
96    #[test]
97    fn reexports_are_the_canonical_dig_rpc_protocol_types() {
98        assert_same_type::<dig_rpc_protocol::Method>(Method::Health);
99        assert_same_type::<dig_rpc_protocol::Tier>(Tier::PublicRead);
100        assert_same_type::<dig_rpc_protocol::ErrorCode>(ErrorCode::MethodNotFound);
101        assert_same_type::<dig_rpc_protocol::ErrorOrigin>(ErrorOrigin::Node);
102        assert_same_type::<dig_rpc_protocol::RpcError>(RpcError::of(
103            ErrorCode::MethodNotFound,
104            "conformance",
105        ));
106        let req: JsonRpcRequest = JsonRpcRequest::new(1, "health", serde_json::Value::Null);
107        assert_same_type::<dig_rpc_protocol::JsonRpcRequest>(req);
108        let _: fn(JsonRpcResponse) = |_| {};
109    }
110}