1use std::sync::Arc;
17
18use axum::{
19 extract::Path,
20 http::StatusCode,
21 response::{IntoResponse, Response},
22 routing::{get, post},
23 Extension, Json, Router,
24};
25use serde_json::{json, Value};
26
27use tidepool_rpc::cache::CacheStore;
28use tidepool_rpc::cnft::CnftStore;
29use tidepool_rpc::upstream::UpstreamClient;
30
31use crate::dispatcher::{
32 handle_create_webhook, handle_delete_webhook, handle_edit_webhook, handle_get_all_webhooks,
33 handle_get_balances, handle_get_transactions, handle_get_transactions_by_address,
34 handle_get_webhook_by_id, Ctx,
35};
36use crate::json_rpc::{codes, JsonRpcRequest};
37
38type RestCtx = Ctx<dyn CnftStore, dyn CacheStore, dyn UpstreamClient>;
39
40pub fn router<S: Clone + Send + Sync + 'static>() -> Router<S> {
48 Router::new()
49 .route("/v0/addresses/{address}/balances", get(get_balances_rest))
51 .route(
53 "/v0/addresses/{address}/transactions",
54 get(get_transactions_by_address_rest),
55 )
56 .route("/v0/transactions", post(get_transactions_rest))
57 .route(
59 "/v0/webhooks",
60 get(list_webhooks_rest).post(create_webhook_rest),
61 )
62 .route(
63 "/v0/webhooks/{id}",
64 get(get_webhook_rest)
65 .put(edit_webhook_rest)
66 .delete(delete_webhook_rest),
67 )
68}
69
70async fn get_balances_rest(
76 Path(address): Path<String>,
77 Extension(ctx): Extension<Arc<RestCtx>>,
78) -> Response {
79 let req = synth_request("getBalances", json!([address]));
80 let resp = handle_get_balances(&*ctx, &req).await;
81 rest_response_from_rpc(resp)
82}
83
84async fn get_transactions_by_address_rest(
85 Path(address): Path<String>,
86 Extension(ctx): Extension<Arc<RestCtx>>,
87) -> Response {
88 let req = synth_request("getTransactionsByAddress", json!({ "address": address }));
89 let resp = handle_get_transactions_by_address(&*ctx, &req).await;
90 rest_response_from_rpc(resp)
91}
92
93async fn get_transactions_rest(
94 Extension(ctx): Extension<Arc<RestCtx>>,
95 Json(body): Json<Value>,
96) -> Response {
97 let sigs = body
100 .get("transactions")
101 .cloned()
102 .or_else(|| body.get("signatures").cloned())
103 .unwrap_or(Value::Array(Vec::new()));
104 let req = synth_request("getTransactions", json!({ "signatures": sigs }));
105 let resp = handle_get_transactions(&*ctx, &req).await;
106 rest_response_from_rpc(resp)
107}
108
109async fn create_webhook_rest(
110 Extension(ctx): Extension<Arc<RestCtx>>,
111 Json(body): Json<Value>,
112) -> Response {
113 let req = synth_request("createWebhook", body);
114 let resp = handle_create_webhook(&*ctx, &req).await;
115 rest_response_from_rpc(resp)
116}
117
118async fn list_webhooks_rest(Extension(ctx): Extension<Arc<RestCtx>>) -> Response {
119 let req = synth_request("getAllWebhooks", Value::Null);
120 let resp = handle_get_all_webhooks(&*ctx, &req).await;
121 rest_response_from_rpc(resp)
122}
123
124async fn get_webhook_rest(
125 Path(id): Path<String>,
126 Extension(ctx): Extension<Arc<RestCtx>>,
127) -> Response {
128 let req = synth_request("getWebhookByID", json!({ "webhookID": id }));
129 let resp = handle_get_webhook_by_id(&*ctx, &req).await;
130 rest_response_from_rpc(resp)
131}
132
133async fn edit_webhook_rest(
134 Path(id): Path<String>,
135 Extension(ctx): Extension<Arc<RestCtx>>,
136 Json(mut body): Json<Value>,
137) -> Response {
138 if let Value::Object(ref mut map) = body {
140 map.insert("webhookID".into(), Value::String(id.clone()));
141 } else {
142 body = json!({ "webhookID": id });
143 }
144 let req = synth_request("editWebhook", body);
145 let resp = handle_edit_webhook(&*ctx, &req).await;
146 rest_response_from_rpc(resp)
147}
148
149async fn delete_webhook_rest(
150 Path(id): Path<String>,
151 Extension(ctx): Extension<Arc<RestCtx>>,
152) -> Response {
153 let req = synth_request("deleteWebhook", json!({ "webhookID": id }));
154 let resp = handle_delete_webhook(&*ctx, &req).await;
155 rest_response_from_rpc(resp)
156}
157
158fn synth_request(method: &str, params: Value) -> JsonRpcRequest {
161 JsonRpcRequest {
162 jsonrpc: Some("2.0".into()),
163 id: Value::from(0),
164 method: method.into(),
165 params,
166 }
167}
168
169fn rest_response_from_rpc(mut resp: Value) -> Response {
173 if let Some(err) = resp.get_mut("error").map(Value::take) {
174 let code = err
175 .get("code")
176 .and_then(Value::as_i64)
177 .unwrap_or(i64::from(codes::INTERNAL_ERROR));
178 let status = match code {
181 -32602 => StatusCode::BAD_REQUEST,
182 -32601 => StatusCode::NOT_FOUND,
183 _ => StatusCode::INTERNAL_SERVER_ERROR,
184 };
185 return (status, Json(err)).into_response();
186 }
187 let result = resp.get_mut("result").map_or(Value::Null, Value::take);
188 (StatusCode::OK, Json(result)).into_response()
189}