1use std::{fmt, sync::Arc};
62
63use lazy_static::lazy_static;
64
65pub mod auth;
66pub mod errors;
67pub mod header;
68pub mod methods;
69
70use errors::*;
71
72pub const NEAR_MAINNET_RPC_URL: &str = "https://rpc.mainnet.near.org";
73pub const NEAR_TESTNET_RPC_URL: &str = "https://rpc.testnet.near.org";
74pub const NEAR_MAINNET_ARCHIVAL_RPC_URL: &str = "https://archival-rpc.mainnet.near.org";
75pub const NEAR_TESTNET_ARCHIVAL_RPC_URL: &str = "https://archival-rpc.testnet.near.org";
76
77lazy_static! {
78 static ref DEFAULT_CONNECTOR: JsonRpcClientConnector = JsonRpcClient::new_client();
79}
80
81#[derive(Clone)]
83pub struct JsonRpcClientConnector {
84 client: reqwest::Client,
85}
86
87impl JsonRpcClientConnector {
88 pub fn connect<U: AsUrl>(&self, server_addr: U) -> JsonRpcClient {
90 log::debug!("returned a new JSONRPC client handle");
91
92 JsonRpcClient {
93 inner: Arc::new(JsonRpcInnerClient {
94 server_addr: server_addr.to_string(),
95 client: self.client.clone(),
96 }),
97 headers: reqwest::header::HeaderMap::new(),
98 }
99 }
100}
101
102struct JsonRpcInnerClient {
103 server_addr: String,
104 client: reqwest::Client,
105}
106
107#[derive(Clone)]
108pub struct JsonRpcClient {
133 inner: Arc<JsonRpcInnerClient>,
134 headers: reqwest::header::HeaderMap,
135}
136
137pub type MethodCallResult<T, E> = Result<T, JsonRpcError<E>>;
138
139impl JsonRpcClient {
140 pub fn connect<U: AsUrl>(server_addr: U) -> JsonRpcClient {
153 DEFAULT_CONNECTOR.connect(server_addr)
154 }
155
156 pub fn server_addr(&self) -> &str {
169 &self.inner.server_addr
170 }
171
172 pub async fn call<M>(&self, method: M) -> MethodCallResult<M::Response, M::Error>
194 where
195 M: methods::RpcMethod,
196 {
197 let request_payload = methods::to_json(&method).map_err(|err| {
198 JsonRpcError::TransportError(RpcTransportError::SendError(
199 JsonRpcTransportSendError::PayloadSerializeError(err),
200 ))
201 })?;
202
203 log::debug!("request payload: {:#}", request_payload);
204 log::debug!("request headers: {:#?}", self.headers());
205
206 let request_payload = serde_json::to_vec(&request_payload).map_err(|err| {
207 JsonRpcError::TransportError(RpcTransportError::SendError(
208 JsonRpcTransportSendError::PayloadSerializeError(err.into()),
209 ))
210 })?;
211
212 let request = self
213 .inner
214 .client
215 .post(&self.inner.server_addr)
216 .headers(self.headers.clone())
217 .body(request_payload);
218
219 let response = request.send().await.map_err(|err| {
220 JsonRpcError::TransportError(RpcTransportError::SendError(
221 JsonRpcTransportSendError::PayloadSendError(err),
222 ))
223 })?;
224 log::debug!("response headers: {:#?}", response.headers());
225 match response.status() {
226 reqwest::StatusCode::OK => {}
227 non_ok_status => {
228 return Err(JsonRpcError::ServerError(match non_ok_status {
229 reqwest::StatusCode::UNAUTHORIZED => JsonRpcServerError::ResponseStatusError(
230 JsonRpcServerResponseStatusError::Unauthorized,
231 ),
232 reqwest::StatusCode::TOO_MANY_REQUESTS => {
233 JsonRpcServerError::ResponseStatusError(
234 JsonRpcServerResponseStatusError::TooManyRequests,
235 )
236 }
237 reqwest::StatusCode::BAD_REQUEST => JsonRpcServerError::ResponseStatusError(
238 JsonRpcServerResponseStatusError::BadRequest,
239 ),
240 reqwest::StatusCode::INTERNAL_SERVER_ERROR => {
241 JsonRpcServerError::InternalError {
242 info: Some(String::from("Internal server error")),
243 }
244 }
245 reqwest::StatusCode::SERVICE_UNAVAILABLE => {
246 JsonRpcServerError::ResponseStatusError(
247 JsonRpcServerResponseStatusError::ServiceUnavailable,
248 )
249 }
250 reqwest::StatusCode::REQUEST_TIMEOUT => {
251 JsonRpcServerError::ResponseStatusError(
252 JsonRpcServerResponseStatusError::TimeoutError,
253 )
254 }
255 unexpected => JsonRpcServerError::ResponseStatusError(
256 JsonRpcServerResponseStatusError::Unexpected { status: unexpected },
257 ),
258 }));
259 }
260 }
261 let response_payload = response.bytes().await.map_err(|err| {
262 JsonRpcError::TransportError(RpcTransportError::RecvError(
263 JsonRpcTransportRecvError::PayloadRecvError(err),
264 ))
265 })?;
266 let response_payload = serde_json::from_slice::<serde_json::Value>(&response_payload);
267
268 if let Ok(ref response_payload) = response_payload {
269 log::debug!("response payload: {:#}", response_payload);
270 }
271
272 let response_message = near_jsonrpc_primitives::message::decoded_to_parsed(
273 response_payload.and_then(serde_json::from_value),
274 )
275 .map_err(|err| {
276 JsonRpcError::TransportError(RpcTransportError::RecvError(
277 JsonRpcTransportRecvError::PayloadParseError(err),
278 ))
279 })?;
280
281 if let near_jsonrpc_primitives::message::Message::Response(response) = response_message {
282 return M::parse_handler_response(response.result?)
283 .map_err(|err| {
284 JsonRpcError::TransportError(RpcTransportError::RecvError(
285 JsonRpcTransportRecvError::ResponseParseError(
286 JsonRpcTransportHandlerResponseError::ResultParseError(err),
287 ),
288 ))
289 })?
290 .map_err(|err| JsonRpcError::ServerError(JsonRpcServerError::HandlerError(err)));
291 }
292 Err(JsonRpcError::TransportError(RpcTransportError::RecvError(
293 JsonRpcTransportRecvError::UnexpectedServerResponse(response_message),
294 )))
295 }
296
297 pub fn header<H, D>(self, entry: H) -> D::Output
321 where
322 H: header::HeaderEntry<D>,
323 D: header::HeaderEntryDiscriminant<H>,
324 {
325 D::apply(self, entry)
326 }
327
328 pub fn headers(&self) -> &reqwest::header::HeaderMap {
330 &self.headers
331 }
332
333 pub fn headers_mut(&mut self) -> &mut reqwest::header::HeaderMap {
335 &mut self.headers
336 }
337
338 pub fn new_client() -> JsonRpcClientConnector {
356 let mut headers = reqwest::header::HeaderMap::with_capacity(2);
357 headers.insert(
358 reqwest::header::CONTENT_TYPE,
359 reqwest::header::HeaderValue::from_static("application/json"),
360 );
361
362 log::debug!("initialized a new JSONRPC client connector");
363 JsonRpcClientConnector {
364 client: reqwest::Client::builder()
365 .default_headers(headers)
366 .build()
367 .unwrap(),
368 }
369 }
370
371 pub fn with(client: reqwest::Client) -> JsonRpcClientConnector {
391 JsonRpcClientConnector { client }
392 }
393}
394
395impl fmt::Debug for JsonRpcClient {
396 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
397 let mut builder = f.debug_struct("JsonRpcClient");
398 builder.field("server_addr", &self.inner.server_addr);
399 builder.field("headers", &self.headers);
400 builder.field("client", &self.inner.client);
401 builder.finish()
402 }
403}
404
405mod private {
406 pub trait Sealed: ToString {}
407}
408
409pub trait AsUrl: private::Sealed {}
410
411impl private::Sealed for String {}
412
413impl AsUrl for String {}
414
415impl private::Sealed for &String {}
416
417impl AsUrl for &String {}
418
419impl private::Sealed for &str {}
420
421impl AsUrl for &str {}
422
423impl private::Sealed for reqwest::Url {}
424
425impl AsUrl for reqwest::Url {}
426
427#[cfg(test)]
428mod tests {
429 use crate::{methods, JsonRpcClient};
430
431 #[tokio::test]
432 async fn chk_status_testnet() {
433 let client = JsonRpcClient::connect("https://rpc.testnet.near.org");
434
435 let status = client.call(methods::status::RpcStatusRequest).await;
436
437 assert!(
438 matches!(status, Ok(methods::status::RpcStatusResponse { .. })),
439 "expected an Ok(RpcStatusResponse), found [{:?}]",
440 status
441 );
442 }
443
444 #[tokio::test]
445 #[cfg(feature = "any")]
446 async fn any_typed_ok() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
447 let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com");
448
449 let tx_status = client
450 .call(methods::any::<methods::tx::RpcTransactionStatusRequest>(
451 "tx",
452 serde_json::json!([
453 "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
454 "miraclx.near",
455 ]),
456 ))
457 .await;
458
459 assert!(
460 matches!(
461 tx_status,
462 Ok(methods::tx::RpcTransactionResponse { ref final_execution_outcome, .. })
463 if final_execution_outcome.clone().unwrap().into_outcome().transaction.signer_id == "miraclx.near"
464 && final_execution_outcome.clone().unwrap().into_outcome().transaction.hash == "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U".parse()?
465 ),
466 "expected an Ok(RpcTransactionStatusResponse) with matching signer_id + hash, found [{:?}]",
467 tx_status
468 );
469
470 Ok(())
471 }
472
473 #[tokio::test]
474 #[cfg(feature = "any")]
475 async fn any_typed_err() -> Result<(), Box<dyn std::error::Error>> {
476 let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com");
477
478 let tx_error = client
479 .call(methods::any::<methods::tx::RpcTransactionStatusRequest>(
480 "tx",
481 serde_json::json!([
482 "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8D",
483 "youser.near",
484 ]),
485 ))
486 .await
487 .expect_err("request must not succeed");
488
489 assert!(
490 matches!(
491 tx_error.handler_error(),
492 Some(methods::tx::RpcTransactionError::UnknownTransaction {
493 requested_transaction_hash
494 })
495 if requested_transaction_hash.to_string() == "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8D"
496 ),
497 "expected an Ok(RpcTransactionError::UnknownTransaction) with matching hash, found [{:?}]",
498 tx_error
499 );
500
501 Ok(())
502 }
503
504 #[tokio::test]
505 #[cfg(feature = "any")]
506 async fn any_untyped_ok() {
507 let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com");
508
509 let status = client
510 .call(
511 methods::any::<Result<serde_json::Value, serde_json::Value>>(
512 "tx",
513 serde_json::json!([
514 "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
515 "miraclx.near",
516 ]),
517 ),
518 )
519 .await
520 .expect("request must not fail");
521
522 assert_eq!(
523 status["transaction"]["signer_id"], "miraclx.near",
524 "expected a tx_status with matching signer_id, [{:#}]",
525 status
526 );
527 assert_eq!(
528 status["transaction"]["hash"], "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8U",
529 "expected a tx_status with matching hash, [{:#}]",
530 status
531 );
532 }
533
534 #[tokio::test]
535 #[cfg(feature = "any")]
536 async fn any_untyped_err() {
537 let client = JsonRpcClient::connect("https://archival-rpc.mainnet.fastnear.com");
538
539 let tx_error = client
540 .call(
541 methods::any::<Result<serde_json::Value, serde_json::Value>>(
542 "tx",
543 serde_json::json!([
544 "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8D",
545 "youser.near",
546 ]),
547 ),
548 )
549 .await
550 .expect_err("request must not succeed");
551 let tx_error = tx_error
552 .handler_error()
553 .expect("expected a handler error from query request");
554
555 assert_eq!(
556 tx_error["info"]["requested_transaction_hash"],
557 "9FtHUFBQsZ2MG77K3x3MJ9wjX3UT8zE1TczCrhZEcG8D",
558 "expected an error with matching hash, [{:#}]",
559 tx_error
560 );
561 assert_eq!(
562 tx_error["name"], "UNKNOWN_TRANSACTION",
563 "expected an UnknownTransaction, [{:#}]",
564 tx_error
565 );
566 }
567}