1use reqwest::header;
20use serde::{de::DeserializeOwned, Deserialize, Serialize};
21use serde_json::Value;
22use std::collections::HashMap;
23use thiserror::Error;
24use uuid::Uuid;
25
26pub use crate::trp::spec::{
27 InputNotResolvedDiagnostic, MissingTxArgDiagnostic, ResolveParams, SubmitParams,
28 SubmitResponse, SubmitWitness, TxEnvelope, TxScriptFailureDiagnostic, UnsupportedTirDiagnostic,
29};
30
31mod spec;
32
33#[derive(Debug, Error)]
35pub enum Error {
36 #[error("network error: {0}")]
37 NetworkError(#[from] reqwest::Error),
38
39 #[error("HTTP error {0}: {1}")]
40 HttpError(u16, String),
41
42 #[error("Failed to deserialize response: {0}")]
43 DeserializationError(String),
44
45 #[error("({0}) {1}")]
46 GenericRpcError(i32, String, Option<Value>),
47
48 #[error("Unknown error: {0}")]
49 UnknownError(String),
50
51 #[error("TIR version {provided} is not supported, expected {expected}", provided = .0.provided, expected = .0.expected)]
52 UnsupportedTir(UnsupportedTirDiagnostic),
53
54 #[error("invalid TIR envelope")]
55 InvalidTirEnvelope,
56
57 #[error("failed to decode IR bytes")]
58 InvalidTirBytes,
59
60 #[error("only txs from Conway era are supported")]
61 UnsupportedTxEra,
62
63 #[error("node can't resolve txs while running at era {era}")]
64 UnsupportedEra { era: String },
65
66 #[error("missing argument `{key}` of type {ty}", key = .0.key, ty = .0.ty)]
67 MissingTxArg(MissingTxArgDiagnostic),
68
69 #[error("input `{name}` not resolved", name = .0.name)]
70 InputNotResolved(InputNotResolvedDiagnostic),
71
72 #[error("tx script returned failure")]
73 TxScriptFailure(TxScriptFailureDiagnostic),
74}
75
76impl Error {
77 fn generic(payload: JsonRpcError) -> Self {
78 Self::GenericRpcError(payload.code, payload.message, payload.data)
79 }
80}
81
82fn expect_json_rpc_error_data<T: DeserializeOwned>(payload: JsonRpcError) -> Result<T, Error> {
83 let Some(data) = payload.data.clone() else {
84 return Err(Error::generic(payload));
85 };
86
87 let Ok(data) = serde_json::from_value(data.clone()) else {
88 return Err(Error::generic(payload));
89 };
90
91 Ok(data)
92}
93
94impl From<JsonRpcError> for Error {
95 fn from(error: JsonRpcError) -> Self {
96 match error.code {
97 -32000 => match expect_json_rpc_error_data(error) {
98 Ok(data) => Error::UnsupportedTir(data),
99 Err(e) => e,
100 },
101 -32001 => match expect_json_rpc_error_data(error) {
102 Ok(data) => Error::MissingTxArg(data),
103 Err(e) => e,
104 },
105 -32002 => match expect_json_rpc_error_data(error) {
106 Ok(data) => Error::InputNotResolved(data),
107 Err(e) => e,
108 },
109 -32003 => match expect_json_rpc_error_data(error) {
110 Ok(data) => Error::TxScriptFailure(data),
111 Err(e) => e,
112 },
113 _ => Error::generic(error),
114 }
115 }
116}
117
118#[derive(Debug, Clone)]
119pub struct ClientOptions {
120 pub endpoint: String,
121 pub headers: Option<HashMap<String, String>>,
122}
123
124#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct JsonRpcRequest {
126 pub jsonrpc: String,
127 pub method: String,
128 pub params: serde_json::Value,
129 pub id: String,
130}
131
132#[derive(Debug, Deserialize)]
133struct JsonRpcResponse {
134 result: Option<serde_json::Value>,
135 error: Option<JsonRpcError>,
136}
137
138#[derive(Debug, Deserialize)]
139struct JsonRpcError {
140 code: i32,
141 message: String,
142 data: Option<Value>,
143}
144
145#[derive(Clone)]
147pub struct Client {
148 options: ClientOptions,
149 client: reqwest::Client,
150}
151
152impl Client {
153 pub fn new(options: ClientOptions) -> Self {
154 Self {
155 options,
156 client: reqwest::Client::new(),
157 }
158 }
159
160 pub async fn call(
161 &self,
162 method: &str,
163 params: serde_json::Value,
164 ) -> Result<serde_json::Value, Error> {
165 let mut headers = header::HeaderMap::new();
167 headers.insert(
168 header::CONTENT_TYPE,
169 header::HeaderValue::from_static("application/json"),
170 );
171
172 if let Some(user_headers) = &self.options.headers {
173 for (key, value) in user_headers {
174 if let Ok(header_name) = header::HeaderName::from_bytes(key.as_bytes()) {
175 if let Ok(header_value) = header::HeaderValue::from_str(value) {
176 headers.insert(header_name, header_value);
177 }
178 }
179 }
180 }
181
182 let body = JsonRpcRequest {
184 jsonrpc: "2.0".to_string(),
185 method: method.to_string(),
186 params,
187 id: Uuid::new_v4().to_string(),
188 };
189
190 let response = self
192 .client
193 .post(&self.options.endpoint)
194 .headers(headers)
195 .json(&serde_json::to_value(body).unwrap())
196 .send()
197 .await
198 .map_err(Error::from)?;
199
200 if !response.status().is_success() {
202 return Err(Error::HttpError(
203 response.status().as_u16(),
204 response.status().to_string(),
205 ));
206 }
207
208 let result: JsonRpcResponse = response
210 .json()
211 .await
212 .map_err(|e| Error::DeserializationError(e.to_string()))?;
213
214 if let Some(error) = result.error {
216 return Err(Error::from(error));
217 }
218
219 result
220 .result
221 .ok_or_else(|| Error::UnknownError("No result in response".to_string()))
222 }
223
224 pub async fn resolve(&self, request: ResolveParams) -> Result<TxEnvelope, Error> {
225 let params = serde_json::to_value(request).unwrap();
226
227 let response = self.call("trp.resolve", params).await?;
228
229 let out = serde_json::from_value(response)
231 .map_err(|e| Error::DeserializationError(e.to_string()))?;
232
233 Ok(out)
234 }
235
236 pub async fn submit(&self, request: SubmitParams) -> Result<SubmitResponse, Error> {
237 let params = serde_json::to_value(request).unwrap();
238
239 let response = self.call("trp.submit", params).await?;
240
241 let out = serde_json::from_value(response)
242 .map_err(|e| Error::DeserializationError(e.to_string()))?;
243
244 Ok(out)
245 }
246}