1use serde::{Deserialize, Serialize};
2use utoipa::ToSchema;
3
4mod json_rpc;
5pub use json_rpc::*;
6
7mod solana;
8pub use solana::*;
9
10mod stellar;
11pub use stellar::*;
12
13mod evm;
14pub use evm::*;
15
16mod error;
17pub use error::*;
18
19#[derive(Debug, Serialize, Deserialize, ToSchema, PartialEq)]
20#[serde(untagged)]
21pub enum NetworkRpcResult {
22 Solana(SolanaRpcResult),
23 Stellar(StellarRpcResult),
24 Evm(EvmRpcResult),
25}
26
27#[derive(Debug, Serialize, Deserialize, ToSchema, PartialEq)]
28#[serde(untagged)]
29#[serde(deny_unknown_fields)]
30pub enum NetworkRpcRequest {
31 Solana(SolanaRpcRequest),
32 Stellar(StellarRpcRequest),
33 Evm(EvmRpcRequest),
34}
35
36pub fn convert_to_internal_rpc_request(
67 request: serde_json::Value,
68 network_type: &crate::models::NetworkType,
69) -> Result<JsonRpcRequest<NetworkRpcRequest>, crate::models::ApiError> {
70 let jsonrpc = request
71 .get("jsonrpc")
72 .and_then(|v| v.as_str())
73 .unwrap_or("2.0")
74 .to_string();
75
76 let id = match request.get("id") {
77 Some(id_value) => match id_value {
78 serde_json::Value::String(s) => Some(JsonRpcId::String(s.clone())),
79 serde_json::Value::Number(n) => {
80 n.as_i64().map(JsonRpcId::Number).map(Some).ok_or_else(|| {
81 crate::models::ApiError::BadRequest(
82 "Invalid 'id' field: must be a string, integer, or null".to_string(),
83 )
84 })?
85 }
86 serde_json::Value::Null => None,
87 _ => {
88 return Err(crate::models::ApiError::BadRequest(
89 "Invalid 'id' field: must be a string, integer, or null".to_string(),
90 ))
91 }
92 },
93 None => Some(JsonRpcId::Number(1)), };
95
96 let method = request
97 .get("method")
98 .and_then(|v| v.as_str())
99 .ok_or_else(|| crate::models::ApiError::BadRequest("Missing 'method' field".to_string()))?;
100
101 match network_type {
102 crate::models::NetworkType::Evm => {
103 let params = request
104 .get("params")
105 .cloned()
106 .unwrap_or(serde_json::Value::Null);
107
108 Ok(JsonRpcRequest {
109 jsonrpc,
110 params: NetworkRpcRequest::Evm(crate::models::EvmRpcRequest::RawRpcRequest {
111 method: method.to_string(),
112 params,
113 }),
114 id,
115 })
116 }
117 crate::models::NetworkType::Solana => {
118 let solana_request: crate::models::SolanaRpcRequest =
119 serde_json::from_value(request.clone()).map_err(|e| {
120 crate::models::ApiError::BadRequest(format!(
121 "Invalid Solana RPC request: {}",
122 e
123 ))
124 })?;
125
126 Ok(JsonRpcRequest {
127 jsonrpc,
128 params: NetworkRpcRequest::Solana(solana_request),
129 id,
130 })
131 }
132 crate::models::NetworkType::Stellar => {
133 let stellar_request: crate::models::StellarRpcRequest =
134 serde_json::from_value(request.clone()).map_err(|e| {
135 crate::models::ApiError::BadRequest(format!(
136 "Invalid Stellar RPC request: {}",
137 e
138 ))
139 })?;
140
141 Ok(JsonRpcRequest {
142 jsonrpc,
143 params: NetworkRpcRequest::Stellar(stellar_request),
144 id,
145 })
146 }
147 }
148}
149
150#[cfg(test)]
151mod tests {
152 use super::*;
153 use crate::models::{EvmRpcRequest, NetworkType, SolanaRpcRequest, StellarRpcRequest};
154 use serde_json::json;
155
156 #[test]
157 fn test_convert_evm_standard_request() {
158 let request = json!({
159 "jsonrpc": "2.0",
160 "method": "eth_getBalance",
161 "params": ["0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "latest"],
162 "id": 1
163 });
164
165 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
166
167 assert_eq!(result.jsonrpc, "2.0");
168 assert_eq!(result.id, Some(JsonRpcId::Number(1)));
169
170 match result.params {
171 NetworkRpcRequest::Evm(EvmRpcRequest::RawRpcRequest { method, params }) => {
172 assert_eq!(method, "eth_getBalance");
173 assert_eq!(params[0], "0x742d35Cc6634C0532925a3b844Bc454e4438f44e");
174 assert_eq!(params[1], "latest");
175 }
176 _ => unreachable!("Expected EVM RawRpcRequest"),
177 }
178 }
179
180 #[test]
181 fn test_convert_evm_missing_method_field() {
182 let request = json!({
183 "jsonrpc": "2.0",
184 "params": ["0x742d35Cc6634C0532925a3b844Bc454e4438f44e", "latest"],
185 "id": 1
186 });
187
188 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
189 assert!(result.is_err());
190 }
191
192 #[test]
193 fn test_convert_evm_with_defaults() {
194 let request = json!({
195 "method": "eth_blockNumber"
196 });
197
198 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
199
200 assert_eq!(result.jsonrpc, "2.0");
201 assert_eq!(result.id, Some(JsonRpcId::Number(1)));
202
203 match result.params {
204 NetworkRpcRequest::Evm(EvmRpcRequest::RawRpcRequest { method, params }) => {
205 assert_eq!(method, "eth_blockNumber");
206 assert_eq!(params, serde_json::Value::Null);
207 }
208 _ => unreachable!("Expected EVM RawRpcRequest"),
209 }
210 }
211
212 #[test]
213 fn test_convert_evm_with_custom_jsonrpc_and_id() {
214 let request = json!({
215 "jsonrpc": "1.0",
216 "method": "eth_chainId",
217 "params": [],
218 "id": 42
219 });
220
221 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
222
223 assert_eq!(result.jsonrpc, "1.0");
224 assert_eq!(result.id, Some(JsonRpcId::Number(42)));
225 }
226
227 #[test]
228 fn test_convert_evm_with_string_id() {
229 let request = json!({
230 "jsonrpc": "2.0",
231 "method": "eth_chainId",
232 "params": [],
233 "id": "test-id"
234 });
235
236 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
237
238 assert_eq!(result.id, Some(JsonRpcId::String("test-id".to_string())));
240 }
241
242 #[test]
243 fn test_convert_evm_with_null_id() {
244 let request = json!({
245 "jsonrpc": "2.0",
246 "method": "eth_chainId",
247 "params": [],
248 "id": null
249 });
250
251 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
252
253 assert_eq!(result.id, None);
255 }
256
257 #[test]
258 fn test_convert_evm_with_object_params() {
259 let request = json!({
260 "jsonrpc": "2.0",
261 "method": "eth_getTransactionByHash",
262 "params": {
263 "hash": "0x123",
264 "full": true
265 },
266 "id": 1
267 });
268
269 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
270
271 match result.params {
272 NetworkRpcRequest::Evm(EvmRpcRequest::RawRpcRequest { method, params }) => {
273 assert_eq!(method, "eth_getTransactionByHash");
274 assert_eq!(params["hash"], "0x123");
275 assert_eq!(params["full"], true);
276 }
277 _ => unreachable!("Expected EVM RawRpcRequest"),
278 }
279 }
280
281 #[test]
282 fn test_convert_solana_fee_estimate_request() {
283 let request = json!({
284 "jsonrpc": "2.0",
285 "method": "feeEstimate",
286 "params": {
287 "transaction": "base64encodedtransaction",
288 "fee_token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v" },
290 "id": 1
291 });
292
293 let result = convert_to_internal_rpc_request(request, &NetworkType::Solana).unwrap();
294
295 assert_eq!(result.jsonrpc, "2.0");
296 assert_eq!(result.id, Some(JsonRpcId::Number(1)));
297
298 match result.params {
299 NetworkRpcRequest::Solana(solana_request) => {
300 match solana_request {
302 SolanaRpcRequest::FeeEstimate(_) => {}
303 _ => unreachable!("Expected FeeEstimate variant"),
304 }
305 }
306 _ => unreachable!("Expected Solana request"),
307 }
308 }
309
310 #[test]
311 fn test_convert_solana_get_supported_tokens_request() {
312 let request = json!({
313 "jsonrpc": "2.0",
314 "method": "getSupportedTokens",
315 "params": {},
316 "id": 2
317 });
318
319 let result = convert_to_internal_rpc_request(request, &NetworkType::Solana).unwrap();
320
321 assert_eq!(result.jsonrpc, "2.0");
322 assert_eq!(result.id, Some(JsonRpcId::Number(2)));
323
324 match result.params {
325 NetworkRpcRequest::Solana(solana_request) => match solana_request {
326 SolanaRpcRequest::GetSupportedTokens(_) => {}
327 _ => unreachable!("Expected GetSupportedTokens variant"),
328 },
329 _ => unreachable!("Expected Solana request"),
330 }
331 }
332
333 #[test]
334 fn test_convert_solana_transfer_transaction_request() {
335 let request = json!({
336 "jsonrpc": "2.0",
337 "method": "transferTransaction",
338 "params": {
339 "amount": 1000000,
340 "token": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v", "source": "source_address",
342 "destination": "destination_address"
343 },
344 "id": 3
345 });
346
347 let result = convert_to_internal_rpc_request(request, &NetworkType::Solana).unwrap();
348
349 match result.params {
350 NetworkRpcRequest::Solana(solana_request) => match solana_request {
351 SolanaRpcRequest::TransferTransaction(_) => {}
352 _ => unreachable!("Expected TransferTransaction variant"),
353 },
354 _ => unreachable!("Expected Solana request"),
355 }
356 }
357
358 #[test]
359 fn test_convert_solana_invalid_request() {
360 let request = json!({
361 "jsonrpc": "2.0",
362 "method": "invalidMethod",
363 "params": {},
364 "id": 1
365 });
366
367 let result = convert_to_internal_rpc_request(request, &NetworkType::Solana);
368 assert!(result.is_err());
369 }
370
371 #[test]
372 fn test_convert_solana_malformed_request() {
373 let request = json!({
374 "jsonrpc": "2.0",
375 "method": "feeEstimate",
376 "params": {
377 "invalid_field": "value"
378 },
379 "id": 1
380 });
381
382 let result = convert_to_internal_rpc_request(request, &NetworkType::Solana);
383 assert!(result.is_err());
384 }
385
386 #[test]
387 fn test_convert_solana_with_defaults() {
388 let request = json!({
389 "method": "getSupportedTokens",
390 "params": {}
391 });
392
393 let result = convert_to_internal_rpc_request(request, &NetworkType::Solana).unwrap();
394
395 assert_eq!(result.jsonrpc, "2.0");
396 assert_eq!(result.id, Some(JsonRpcId::Number(1)));
397 }
398
399 #[test]
400 fn test_convert_stellar_generic_request() {
401 let request = json!({
402 "jsonrpc": "2.0",
403 "method": "GenericRpcRequest",
404 "params": "test_params",
405 "id": 1
406 });
407
408 let result = convert_to_internal_rpc_request(request, &NetworkType::Stellar).unwrap();
409
410 assert_eq!(result.jsonrpc, "2.0");
411 assert_eq!(result.id, Some(JsonRpcId::Number(1)));
412
413 match result.params {
414 NetworkRpcRequest::Stellar(stellar_request) => match stellar_request {
415 StellarRpcRequest::GenericRpcRequest(params) => {
416 assert_eq!(params, "test_params");
417 }
418 },
419 _ => unreachable!("Expected Stellar request"),
420 }
421 }
422
423 #[test]
424 fn test_convert_stellar_invalid_request() {
425 let request = json!({
426 "jsonrpc": "2.0",
427 "method": "InvalidMethod",
428 "params": {},
429 "id": 1
430 });
431
432 let result = convert_to_internal_rpc_request(request, &NetworkType::Stellar);
433 assert!(result.is_err());
434 }
435
436 #[test]
437 fn test_convert_stellar_with_defaults() {
438 let request = json!({
439 "method": "GenericRpcRequest",
440 "params": "default_test"
441 });
442
443 let result = convert_to_internal_rpc_request(request, &NetworkType::Stellar).unwrap();
444
445 assert_eq!(result.jsonrpc, "2.0");
446 assert_eq!(result.id, Some(JsonRpcId::Number(1)));
447 }
448
449 #[test]
450 fn test_convert_empty_request() {
451 let request = json!({});
452
453 let result_evm = convert_to_internal_rpc_request(request.clone(), &NetworkType::Evm);
454 assert!(result_evm.is_err());
455
456 let result_solana = convert_to_internal_rpc_request(request.clone(), &NetworkType::Solana);
457 assert!(result_solana.is_err());
458
459 let result_stellar = convert_to_internal_rpc_request(request, &NetworkType::Stellar);
460 assert!(result_stellar.is_err());
461 }
462
463 #[test]
464 fn test_convert_null_request() {
465 let request = serde_json::Value::Null;
466
467 let result_evm = convert_to_internal_rpc_request(request.clone(), &NetworkType::Evm);
468 assert!(result_evm.is_err());
469
470 let result_solana = convert_to_internal_rpc_request(request.clone(), &NetworkType::Solana);
471 assert!(result_solana.is_err());
472
473 let result_stellar = convert_to_internal_rpc_request(request, &NetworkType::Stellar);
474 assert!(result_stellar.is_err());
475 }
476
477 #[test]
478 fn test_convert_array_request() {
479 let request = json!([1, 2, 3]);
480
481 let result_evm = convert_to_internal_rpc_request(request.clone(), &NetworkType::Evm);
482 assert!(result_evm.is_err());
483
484 let result_solana = convert_to_internal_rpc_request(request.clone(), &NetworkType::Solana);
485 assert!(result_solana.is_err());
486
487 let result_stellar = convert_to_internal_rpc_request(request, &NetworkType::Stellar);
488 assert!(result_stellar.is_err());
489 }
490
491 #[test]
492 fn test_convert_evm_non_string_method() {
493 let request = json!({
494 "jsonrpc": "2.0",
495 "method": 123,
496 "params": [],
497 "id": 1
498 });
499
500 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
501 assert!(result.is_err());
502 }
503
504 #[test]
505 fn test_convert_with_large_id() {
506 let request = json!({
507 "jsonrpc": "2.0",
508 "method": "eth_chainId",
509 "params": [],
510 "id": 18446744073709551615u64 });
512
513 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
514 assert!(result.is_err());
515
516 if let Err(crate::models::ApiError::BadRequest(msg)) = result {
517 assert!(msg.contains("Invalid 'id' field: must be a string, integer, or null"));
518 } else {
519 panic!("Expected BadRequest error");
520 }
521 }
522
523 #[test]
524 fn test_convert_with_zero_id() {
525 let request = json!({
526 "jsonrpc": "2.0",
527 "method": "eth_chainId",
528 "params": [],
529 "id": 0
530 });
531
532 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
533 assert_eq!(result.id, Some(JsonRpcId::Number(0)));
534 }
535
536 #[test]
537 fn test_convert_evm_empty_method() {
538 let request = json!({
539 "jsonrpc": "2.0",
540 "method": "",
541 "params": [],
542 "id": 1
543 });
544
545 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
546
547 match result.params {
548 NetworkRpcRequest::Evm(EvmRpcRequest::RawRpcRequest { method, params: _ }) => {
549 assert_eq!(method, "");
550 }
551 _ => unreachable!("Expected EVM RawRpcRequest"),
552 }
553 }
554
555 #[test]
556 fn test_convert_evm_very_long_method() {
557 let long_method = "a".repeat(1000);
558 let request = json!({
559 "jsonrpc": "2.0",
560 "method": long_method,
561 "params": [],
562 "id": 1
563 });
564
565 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm).unwrap();
566
567 match result.params {
568 NetworkRpcRequest::Evm(EvmRpcRequest::RawRpcRequest { method, params: _ }) => {
569 assert_eq!(method, long_method);
570 }
571 _ => unreachable!("Expected EVM RawRpcRequest"),
572 }
573 }
574
575 #[test]
576 fn test_convert_with_invalid_id_type_boolean() {
577 let request = json!({
578 "jsonrpc": "2.0",
579 "method": "eth_chainId",
580 "params": [],
581 "id": true
582 });
583
584 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
585 assert!(result.is_err());
586
587 if let Err(crate::models::ApiError::BadRequest(msg)) = result {
588 assert!(msg.contains("Invalid 'id' field: must be a string, integer, or null"));
589 } else {
590 panic!("Expected BadRequest error");
591 }
592 }
593
594 #[test]
595 fn test_convert_with_invalid_id_type_array() {
596 let request = json!({
597 "jsonrpc": "2.0",
598 "method": "eth_chainId",
599 "params": [],
600 "id": [1, 2, 3]
601 });
602
603 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
604 assert!(result.is_err());
605
606 if let Err(crate::models::ApiError::BadRequest(msg)) = result {
607 assert!(msg.contains("Invalid 'id' field: must be a string, integer, or null"));
608 } else {
609 panic!("Expected BadRequest error");
610 }
611 }
612
613 #[test]
614 fn test_convert_with_invalid_id_type_object() {
615 let request = json!({
616 "jsonrpc": "2.0",
617 "method": "eth_chainId",
618 "params": [],
619 "id": {"nested": "object"}
620 });
621
622 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
623 assert!(result.is_err());
624
625 if let Err(crate::models::ApiError::BadRequest(msg)) = result {
626 assert!(msg.contains("Invalid 'id' field: must be a string, integer, or null"));
627 } else {
628 panic!("Expected BadRequest error");
629 }
630 }
631
632 #[test]
633 fn test_convert_with_fractional_id() {
634 let request = json!({
635 "jsonrpc": "2.0",
636 "method": "eth_chainId",
637 "params": [],
638 "id": 42.5
639 });
640
641 let result = convert_to_internal_rpc_request(request, &NetworkType::Evm);
642 assert!(result.is_err());
643
644 if let Err(crate::models::ApiError::BadRequest(msg)) = result {
645 assert!(msg.contains("Invalid 'id' field: must be a string, integer, or null"));
646 } else {
647 panic!("Expected BadRequest error");
648 }
649 }
650}