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			.request_timeout(timeout)
50			.build(addr)
51			.map_err(|e| format!("Couldn't create HTTP client: {}", e))?;
52
53		let http_client = OgmiosClients::HttpClient(client);
54
55		// We make a call to get_tip to test HTTP connection
56		http_client
57			.get_tip()
58			.await
59			.map_err(|e| format!("Failed to test HTTP connection: {}", e))?;
60
61		Ok(http_client)
62	} else if addr.starts_with("ws") || addr.starts_with("wss") {
63		let client = WsClientBuilder::default()
64			.request_timeout(timeout)
65			.build(addr.to_owned())
66			.await
67			.map_err(|e| format!("Couldn't create WebSockets client: {}", e))?;
68		Ok(OgmiosClients::WsClient(client))
69	} else {
70		Err(format!("Invalid Schema of URL: '{}'. Expected http, https, ws or wss.", addr))
71	}
72}
73
74impl OgmiosClient for OgmiosClients {
75	/// Sends a JSON-RPC request to the Ogmios server and returns the response.
76	async fn request<T: DeserializeOwned>(
77		&self,
78		method: &str,
79		params: OgmiosParams,
80	) -> Result<T, OgmiosClientError> {
81		log::debug!("request: {}", request_to_json(method, params.clone())?);
82		let response = match self {
83			OgmiosClients::HttpClient(client) => {
84				ClientT::request::<serde_json::Value, _>(client, method, params).await
85			},
86			OgmiosClients::WsClient(client) => {
87				ClientT::request::<serde_json::Value, _>(client, method, params).await
88			},
89		};
90		log::debug!("response: {}", response_to_json(&response));
91
92		serde_json::from_value(response?)
93			.map_err(|err| OgmiosClientError::ResponseError(err.to_string()))
94	}
95}
96
97impl From<jsonrpsee::core::ClientError> for OgmiosClientError {
98	fn from(e: jsonrpsee::core::ClientError) -> Self {
99		match e {
100			jsonrpsee::core::ClientError::ParseError(e) => {
101				OgmiosClientError::ResponseError(e.to_string())
102			},
103			e => OgmiosClientError::RequestError(e.to_string()),
104		}
105	}
106}