ogmios_client/
jsonrpsee.rs

1//! OgmiosClient implementation with jsonrpsee.
2//! Major drawback is that it swallows the error response from the server in case of 400 Bad Request.
3
4use crate::{OgmiosClient, OgmiosClientError, OgmiosParams, query_ledger_state::QueryLedgerState};
5use jsonrpsee::{
6	core::{ClientError, client::ClientT, traits::ToRpcParams},
7	http_client::{HttpClient, HttpClientBuilder},
8	ws_client::{WsClient, WsClientBuilder},
9};
10use serde::de::DeserializeOwned;
11use serde_json::json;
12use std::time::Duration;
13
14/// Converts the method and parameters to a JSON-RPC request string.
15fn request_to_json(method: &str, params: impl ToRpcParams) -> Result<String, OgmiosClientError> {
16	let params = params
17		.to_rpc_params()
18		.map_err(|err| OgmiosClientError::ParametersError(err.to_string()))?
19		.unwrap_or_default();
20
21	let req = json!({
22		"method": method,
23		"params": params
24	});
25
26	serde_json::to_string(&req).map_err(|err| OgmiosClientError::ParametersError(err.to_string()))
27}
28
29/// Converts the response to a JSON string.
30fn response_to_json(resp: &Result<serde_json::Value, ClientError>) -> String {
31	match &resp {
32		Ok(resp) => serde_json::to_string(&resp).unwrap(),
33		Err(jsonrpsee::core::ClientError::Call(err)) => serde_json::to_string(&err).unwrap(),
34		Err(err) => err.to_string(),
35	}
36}
37
38/// Enum that represents the ogmios client that works either with HTTP or WebSockets.
39pub enum OgmiosClients {
40	HttpClient(HttpClient),
41	WsClient(WsClient),
42}
43
44/// Returns client that works either with HTTP or WebSockets.
45/// HTTP does not return JSON-RPC error body in case of 400 Bad Request.
46pub async fn client_for_url(addr: &str, timeout: Duration) -> Result<OgmiosClients, String> {
47	if addr.starts_with("http") || addr.starts_with("https") {
48		let client = HttpClientBuilder::default()
49			.max_response_size(250 * 1024 * 1024)
50			.request_timeout(timeout)
51			.build(addr)
52			.map_err(|e| format!("Couldn't create HTTP client: {}", e))?;
53
54		let http_client = OgmiosClients::HttpClient(client);
55
56		// We make a call to get_tip to test HTTP connection
57		http_client
58			.get_tip()
59			.await
60			.map_err(|e| format!("Failed to test HTTP connection: {}", e))?;
61
62		Ok(http_client)
63	} else if addr.starts_with("ws") || addr.starts_with("wss") {
64		let client = WsClientBuilder::default()
65			.max_response_size(250 * 1024 * 1024)
66			.request_timeout(timeout)
67			.build(addr.to_owned())
68			.await
69			.map_err(|e| format!("Couldn't create WebSockets client: {}", e))?;
70		Ok(OgmiosClients::WsClient(client))
71	} else {
72		Err(format!("Invalid Schema of URL: '{}'. Expected http, https, ws or wss.", addr))
73	}
74}
75
76impl OgmiosClient for OgmiosClients {
77	/// Sends a JSON-RPC request to the Ogmios server and returns the response.
78	async fn request<T: DeserializeOwned>(
79		&self,
80		method: &str,
81		params: OgmiosParams,
82	) -> Result<T, OgmiosClientError> {
83		log::debug!("request: {}", request_to_json(method, params.clone())?);
84		let response = match self {
85			OgmiosClients::HttpClient(client) => {
86				ClientT::request::<serde_json::Value, _>(client, method, params).await
87			},
88			OgmiosClients::WsClient(client) => {
89				ClientT::request::<serde_json::Value, _>(client, method, params).await
90			},
91		};
92		log::debug!("response: {}", response_to_json(&response));
93
94		serde_json::from_value(response?)
95			.map_err(|err| OgmiosClientError::ResponseError(err.to_string()))
96	}
97}
98
99impl From<jsonrpsee::core::ClientError> for OgmiosClientError {
100	fn from(e: jsonrpsee::core::ClientError) -> Self {
101		match e {
102			jsonrpsee::core::ClientError::ParseError(e) => {
103				OgmiosClientError::ResponseError(e.to_string())
104			},
105			e => OgmiosClientError::RequestError(e.to_string()),
106		}
107	}
108}