rialo_api_types/
requests.rs

1// Copyright (c) Subzero Labs, Inc.
2// SPDX-License-Identifier: Apache-2.0
3
4//! Request types for node RPC handlers
5//!
6//! These types provide strongly-typed deserialization for RPC request parameters
7//! in node handlers, replacing manual parameter parsing.
8
9use serde::{Deserialize, Serialize};
10use validator::Validate;
11
12use crate::{
13    messages::send_transaction::SendTransactionConfig,
14    validation::{
15        validate_airdrop_amount_i64, validate_base58, validate_pubkey, validate_signature,
16        validate_transaction_data,
17    },
18    GetBlockRequest, GetSignatureStatusesRequest,
19};
20
21/// Request type for getSubscriptions RPC handler
22#[derive(Debug, Deserialize, Serialize, Clone, Validate)]
23pub struct GetSubscriptionsRequest {
24    #[validate(length(min = 1, message = "Subscriber pubkey cannot be empty"))]
25    #[validate(custom(function = validate_pubkey))]
26    pub subscriber: String,
27
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub nonce: Option<String>,
30
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub limit: Option<u64>,
33}
34
35impl GetSubscriptionsRequest {
36    pub fn new(subscriber: String, nonce: Option<String>, limit: Option<u64>) -> Self {
37        Self {
38            subscriber,
39            nonce,
40            limit,
41        }
42    }
43}
44
45/// Request type for getTriggeredTransactions RPC handler
46#[derive(Debug, Deserialize, Serialize, Clone, Validate)]
47pub struct GetTriggeredTransactionsRequest {
48    #[validate(length(min = 1, message = "Subscription pubkey cannot be empty"))]
49    #[validate(custom(function = validate_pubkey))]
50    pub subscription_pubkey: String,
51
52    #[serde(skip_serializing_if = "Option::is_none")]
53    pub limit: Option<String>,
54}
55
56impl GetTriggeredTransactionsRequest {
57    pub fn new(subscription_pubkey: String, limit: Option<String>) -> Self {
58        Self {
59            subscription_pubkey,
60            limit,
61        }
62    }
63}
64
65/// Request type for sendTransaction RPC handler
66#[derive(Debug, Deserialize, Serialize, Clone, Validate, PartialEq)]
67pub struct SendTransactionRequest {
68    #[validate(length(min = 1, message = "Transaction cannot be empty"))]
69    #[validate(custom(function = validate_transaction_data))]
70    pub transaction: String,
71
72    #[validate(nested)]
73    pub config: crate::messages::send_transaction::SendTransactionConfig,
74}
75
76impl SendTransactionRequest {
77    pub fn new(
78        transaction: String,
79        config: crate::messages::send_transaction::SendTransactionConfig,
80    ) -> Self {
81        Self {
82            transaction,
83            config,
84        }
85    }
86}
87
88/// Request type for getTransaction RPC handler
89#[derive(Debug, Deserialize, Serialize, Clone, Validate)]
90pub struct GetTransactionRequest {
91    #[validate(length(min = 1, message = "Signature cannot be empty"))]
92    #[validate(custom(function = validate_signature))]
93    pub signature: String,
94
95    #[serde(skip_serializing_if = "Option::is_none")]
96    pub config: Option<serde_json::Value>,
97}
98
99impl GetTransactionRequest {
100    pub fn new(signature: String, config: Option<serde_json::Value>) -> Self {
101        Self { signature, config }
102    }
103
104    pub fn new_simple(signature: String) -> Self {
105        Self {
106            signature,
107            config: Some(serde_json::json!({"encoding": "json"})),
108        }
109    }
110}
111
112/// Request type for isBlockhashValid RPC handler
113#[derive(Debug, Deserialize, Serialize, Clone, Validate)]
114pub struct IsBlockhashValidRequest {
115    #[validate(length(min = 1, message = "Blockhash cannot be empty"))]
116    #[validate(custom(function = validate_base58))]
117    pub blockhash: String,
118}
119
120impl IsBlockhashValidRequest {
121    pub fn new(blockhash: String) -> Self {
122        Self { blockhash }
123    }
124
125    pub fn new_simple(blockhash: String) -> Self {
126        Self { blockhash }
127    }
128}
129
130/// Request type for requestAirdrop RPC handler
131#[derive(Debug, Deserialize, Serialize, Clone, Validate)]
132pub struct RequestAirdropRequest {
133    /// Public key of the account to airdrop to, in base58 encoding
134    #[validate(length(min = 1, message = "Pubkey cannot be empty"))]
135    #[validate(custom(function = validate_pubkey))]
136    pub pubkey: String,
137
138    /// Amount of lamports to airdrop  
139    #[validate(custom(function = validate_airdrop_amount_i64))]
140    pub lamports: i64,
141}
142
143impl RequestAirdropRequest {
144    pub fn new(pubkey: String, lamports: i64) -> Self {
145        Self { pubkey, lamports }
146    }
147}
148
149/// Generic RPC request wrapper that can handle both array and object parameter formats
150#[derive(Debug, Clone)]
151pub enum RpcRequestParams<T> {
152    /// Single structured object parameter  
153    Object(T),
154    /// Array of parameters (for backward compatibility)
155    Array(Vec<serde_json::Value>),
156}
157
158impl<T> RpcRequestParams<T>
159where
160    T: for<'de> serde::Deserialize<'de>,
161{
162    /// Try to deserialize from a JSON value
163    pub fn from_value(value: serde_json::Value) -> Result<Self, serde_json::Error> {
164        if value.is_array() {
165            let array = value.as_array().unwrap().clone();
166            Ok(RpcRequestParams::Array(array))
167        } else {
168            let object = serde_json::from_value::<T>(value)?;
169            Ok(RpcRequestParams::Object(object))
170        }
171    }
172
173    /// Get the structured object, converting from array if necessary
174    pub fn into_object(self) -> Result<T, Box<dyn std::error::Error + Send + Sync>> {
175        match self {
176            RpcRequestParams::Object(obj) => Ok(obj),
177            RpcRequestParams::Array(_array) => {
178                // For now, return an error - specific handlers can implement array conversion
179                Err("Array format not supported for this request type".into())
180            }
181        }
182    }
183}
184
185/// Helper trait for converting array parameters to structured objects
186pub trait FromArrayParams: Sized {
187    /// Convert from array of JSON values to structured type
188    fn from_array_params(
189        params: &[serde_json::Value],
190    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>>;
191}
192
193impl FromArrayParams for GetSubscriptionsRequest {
194    fn from_array_params(
195        params: &[serde_json::Value],
196    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
197        if params.is_empty() {
198            return Err("Missing subscriber parameter".into());
199        }
200
201        let subscriber = params[0]
202            .as_str()
203            .ok_or("Subscriber must be a string")?
204            .to_string();
205
206        let nonce = if let Some(nonce_param) = params.get(1) {
207            if nonce_param.is_null() {
208                None
209            } else if let Some(nonce_str) = nonce_param.as_str() {
210                Some(nonce_str.to_string())
211            } else {
212                return Err("Nonce must be a string".into());
213            }
214        } else {
215            None
216        };
217
218        let limit = params.get(2).and_then(|v| v.as_u64());
219
220        Ok(GetSubscriptionsRequest::new(subscriber, nonce, limit))
221    }
222}
223
224impl FromArrayParams for GetTriggeredTransactionsRequest {
225    fn from_array_params(
226        params: &[serde_json::Value],
227    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
228        if params.is_empty() {
229            return Err("Missing subscription_pubkey parameter".into());
230        }
231
232        let subscription_pubkey = params[0]
233            .as_str()
234            .ok_or("Subscription pubkey is not a string")?
235            .to_string();
236
237        let limit = params
238            .get(1)
239            .and_then(|v| v.as_str())
240            .map(|s| s.to_string());
241
242        Ok(GetTriggeredTransactionsRequest::new(
243            subscription_pubkey,
244            limit,
245        ))
246    }
247}
248
249impl FromArrayParams for SendTransactionRequest {
250    fn from_array_params(
251        params: &[serde_json::Value],
252    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
253        if params.len() > 2 {
254            return Err(format!(
255                "Expected one or two request params, got {}",
256                params.len()
257            ))?;
258        }
259        let transaction = params
260            .first()
261            .ok_or_else(|| "Missing the transaction parameter".to_string())?
262            .as_str()
263            .ok_or("Transaction must be a string")?
264            .to_string();
265
266        let config = match params.get(1) {
267            Some(value) => serde_json::from_value::<SendTransactionConfig>(value.clone())
268                .map_err(|e| format!("Failed to parse config: {e}"))?,
269            None => SendTransactionConfig::default(),
270        };
271        Ok(SendTransactionRequest::new(transaction, config))
272    }
273}
274
275impl FromArrayParams for GetTransactionRequest {
276    fn from_array_params(
277        params: &[serde_json::Value],
278    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
279        if params.is_empty() {
280            return Err("Missing signature parameter".into());
281        }
282
283        let signature = params[0]
284            .as_str()
285            .ok_or("Signature must be a string")?
286            .to_string();
287
288        let config = if params.len() > 1 {
289            Some(params[1].clone())
290        } else {
291            None
292        };
293
294        Ok(GetTransactionRequest::new(signature, config))
295    }
296}
297
298impl FromArrayParams for IsBlockhashValidRequest {
299    fn from_array_params(
300        params: &[serde_json::Value],
301    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
302        if params.is_empty() {
303            return Err("Missing blockhash parameter".into());
304        }
305
306        let blockhash = params[0]
307            .as_str()
308            .ok_or("Blockhash must be a string")?
309            .to_string();
310
311        Ok(IsBlockhashValidRequest::new(blockhash))
312    }
313}
314
315impl FromArrayParams for RequestAirdropRequest {
316    fn from_array_params(
317        params: &[serde_json::Value],
318    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
319        if params.is_empty() {
320            return Err("Missing pubkey parameter".into());
321        }
322
323        if params.len() < 2 {
324            return Err("Missing lamports parameter".into());
325        }
326
327        let pubkey = params[0]
328            .as_str()
329            .ok_or("Pubkey must be a string")?
330            .to_string();
331
332        let lamports = params[1].as_i64().ok_or("Lamports must be a number")?;
333
334        Ok(RequestAirdropRequest::new(pubkey, lamports))
335    }
336}
337
338impl FromArrayParams for GetBlockRequest {
339    fn from_array_params(
340        params: &[serde_json::Value],
341    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
342        if params.is_empty() {
343            return Err("Missing the block height parameter".into());
344        }
345        if params.len() > 2 {
346            return Err("Too many parameters: expected either one or two".into());
347        }
348
349        if params.len() == 1 && params[0].is_object() {
350            return Ok(serde_json::from_value(params[0].clone())?);
351        }
352
353        let block_height = params[0]
354            .as_u64()
355            .ok_or("The first parameter must be the block height")?;
356
357        let config = match params.get(1) {
358            Some(value) => Some(
359                serde_json::from_value(value.clone())
360                    .map_err(|e| format!("Failed to parse the config parameter: {e}"))?,
361            ),
362            None => None,
363        };
364
365        Ok(GetBlockRequest {
366            block_height,
367            config,
368        })
369    }
370}
371
372impl FromArrayParams for GetSignatureStatusesRequest {
373    fn from_array_params(
374        params: &[serde_json::Value],
375    ) -> Result<Self, Box<dyn std::error::Error + Send + Sync>> {
376        if params.is_empty() {
377            return Err("Missing the signatures parameter".into());
378        }
379        if params.len() > 2 {
380            return Err("Too many parameters: expected either one or two".into());
381        }
382
383        if params.len() == 1 && params[0].is_object() {
384            return Ok(serde_json::from_value(params[0].clone())?);
385        }
386
387        let signatures: Vec<String> = serde_json::from_value(params[0].clone())
388            .map_err(|e| format!("Failed to parse the signatures parameter: {e}"))?;
389
390        let config = match params.get(1) {
391            Some(value) => Some(
392                serde_json::from_value(value.clone())
393                    .map_err(|e| format!("Failed to parse the config parameter: {e}"))?,
394            ),
395            None => None,
396        };
397
398        Ok(GetSignatureStatusesRequest { signatures, config })
399    }
400}
401
402#[cfg(test)]
403mod tests {
404    use serde_json::{from_value, json, to_value};
405
406    use super::*;
407    use crate::messages::send_transaction::{SendTransactionConfig, TransactionEncoding};
408
409    #[test]
410    fn test_get_subscriptions_request_serialization() {
411        let request = GetSubscriptionsRequest::new(
412            "84astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDVcgri".to_string(),
413            Some("test_nonce".to_string()),
414            Some(10),
415        );
416
417        let json = to_value(&request).unwrap();
418        assert_eq!(
419            json,
420            json!({
421                "subscriber": "84astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDVcgri",
422                "nonce": "test_nonce",
423                "limit": 10
424            })
425        );
426
427        let deserialized: GetSubscriptionsRequest = from_value(json).unwrap();
428        assert_eq!(deserialized.subscriber, request.subscriber);
429        assert_eq!(deserialized.nonce, request.nonce);
430        assert_eq!(deserialized.limit, request.limit);
431    }
432
433    #[test]
434    fn test_get_triggered_transactions_request_serialization() {
435        let request = GetTriggeredTransactionsRequest::new(
436            "84astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDVcgri".to_string(),
437            Some("10".to_string()),
438        );
439
440        let json = to_value(&request).unwrap();
441        assert_eq!(
442            json,
443            json!({
444                "subscription_pubkey": "84astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDVcgri",
445                "limit": "10"
446            })
447        );
448
449        let deserialized: GetTriggeredTransactionsRequest = from_value(json).unwrap();
450        assert_eq!(
451            deserialized.subscription_pubkey,
452            request.subscription_pubkey
453        );
454        assert_eq!(deserialized.limit, request.limit);
455    }
456
457    #[test]
458    fn test_get_transaction_request_serialization() {
459        let request = GetTransactionRequest::new_simple("signature123".to_string());
460
461        let json = to_value(&request).unwrap();
462        assert_eq!(
463            json,
464            json!({
465                "signature": "signature123",
466                "config": {"encoding": "json"}
467            })
468        );
469
470        let deserialized: GetTransactionRequest = from_value(json).unwrap();
471        assert_eq!(deserialized.signature, request.signature);
472    }
473
474    #[test]
475    fn test_from_array_params_get_subscriptions() {
476        let params = vec![
477            json!("84astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDVcgri"),
478            json!("test_nonce"),
479            json!(10),
480        ];
481
482        let request = GetSubscriptionsRequest::from_array_params(&params).unwrap();
483        assert_eq!(
484            request.subscriber,
485            "84astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDVcgri"
486        );
487        assert_eq!(request.nonce, Some("test_nonce".to_string()));
488        assert_eq!(request.limit, Some(10));
489    }
490
491    #[test]
492    fn test_from_array_params_get_subscriptions_invalid_nonce() {
493        let params = vec![
494            json!("84astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDVcgri"),
495            json!(123), // Invalid nonce - should be a string, not a number
496        ];
497
498        let result = GetSubscriptionsRequest::from_array_params(&params);
499        assert!(result.is_err());
500        assert_eq!(result.unwrap_err().to_string(), "Nonce must be a string");
501    }
502
503    #[test]
504    fn test_from_array_params_get_triggered_transactions() {
505        let params = vec![
506            json!("84astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDVcgri"),
507            json!("5"),
508        ];
509
510        let request = GetTriggeredTransactionsRequest::from_array_params(&params).unwrap();
511        assert_eq!(
512            request.subscription_pubkey,
513            "84astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDVcgri"
514        );
515        assert_eq!(request.limit, Some("5".to_string()));
516    }
517
518    #[test]
519    fn test_from_array_params_get_transaction() {
520        let params = vec![json!("signature123"), json!({"encoding": "json"})];
521
522        let request = GetTransactionRequest::from_array_params(&params).unwrap();
523        assert_eq!(request.signature, "signature123");
524        assert!(request.config.is_some());
525    }
526
527    #[test]
528    fn test_from_array_params_is_blockhash_valid() {
529        let params = vec![json!("blockhash123")];
530
531        let request = IsBlockhashValidRequest::from_array_params(&params).unwrap();
532        assert_eq!(request.blockhash, "blockhash123");
533    }
534
535    #[test]
536    fn test_from_array_params_request_airdrop() {
537        let params = vec![
538            json!("83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcgri"),
539            json!(1000000),
540        ];
541
542        let request = RequestAirdropRequest::from_array_params(&params).unwrap();
543        assert_eq!(
544            request.pubkey,
545            "83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcgri"
546        );
547        assert_eq!(request.lamports, 1000000);
548    }
549
550    #[test]
551    fn test_from_array_params_request_airdrop_missing_params() {
552        let params = vec![json!("83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcgri")];
553
554        let result = RequestAirdropRequest::from_array_params(&params);
555        assert!(result.is_err());
556        assert_eq!(
557            result.unwrap_err().to_string(),
558            "Missing lamports parameter"
559        );
560    }
561
562    #[test]
563    fn test_from_array_params_request_airdrop_invalid_type() {
564        let params = vec![
565            json!("83astBRguLMdt2h5U1Tpdq5tjFoJ6noeGwaY3mDLVcgri"),
566            json!("thousand"), // String instead of number
567        ];
568
569        let result = RequestAirdropRequest::from_array_params(&params);
570        assert!(result.is_err());
571        assert_eq!(result.unwrap_err().to_string(), "Lamports must be a number");
572    }
573
574    #[test]
575    fn test_send_transaction_from_array_missing_optional_config() {
576        let encoded_tx = "4hXTCkRzt9WyecNzV1XPgCDfGAZzQKNxLXgynz5QDuWWPSAZBZSHptvWRL3BjCvzUXRdKvHL2b7yGrRQcWyaqsaBCncVG7BFggS8w9snUts67BSh3EqKpXLUm5UMHfD7ZBe9GhARjbNQMLJ1QD3Spr6oMTBU6EhdB4RD8CP2xUxr2u3d6fos36PD98XS6oX8TQjLpsMwncs5DAMiD4nNnR8NBfyghGCWvCVifVwvA8B8TJxE1aiyiv2L429BCWfyzAme5sZW8rDb14NeCQHhZbtNqfXhcp2tAnaAT";
577        let params = vec![json!(encoded_tx)];
578        assert_eq!(
579            SendTransactionRequest::from_array_params(&params)
580                .expect("failed to parse a transaction from params"),
581            SendTransactionRequest {
582                transaction: encoded_tx.to_string(),
583                config: SendTransactionConfig::default()
584            }
585        );
586    }
587
588    #[test]
589    fn test_send_transaction_from_array_default_config() {
590        let encoded_tx = "4hXTCkRzt9WyecNzV1XPgCDfGAZzQKNxLXgynz5QDuWWPSAZBZSHptvWRL3BjCvzUXRdKvHL2b7yGrRQcWyaqsaBCncVG7BFggS8w9snUts67BSh3EqKpXLUm5UMHfD7ZBe9GhARjbNQMLJ1QD3Spr6oMTBU6EhdB4RD8CP2xUxr2u3d6fos36PD98XS6oX8TQjLpsMwncs5DAMiD4nNnR8NBfyghGCWvCVifVwvA8B8TJxE1aiyiv2L429BCWfyzAme5sZW8rDb14NeCQHhZbtNqfXhcp2tAnaAT";
591        let params = vec![json!(encoded_tx), json!({})];
592        assert_eq!(
593            SendTransactionRequest::from_array_params(&params)
594                .expect("failed to parse a transaction from params"),
595            SendTransactionRequest {
596                transaction: encoded_tx.to_string(),
597                config: SendTransactionConfig::default(),
598            }
599        );
600    }
601
602    #[test]
603    fn test_send_transaction_from_array_config() {
604        let encoded_tx = "4hXTCkRzt9WyecNzV1XPgCDfGAZzQKNxLXgynz5QDuWWPSAZBZSHptvWRL3BjCvzUXRdKvHL2b7yGrRQcWyaqsaBCncVG7BFggS8w9snUts67BSh3EqKpXLUm5UMHfD7ZBe9GhARjbNQMLJ1QD3Spr6oMTBU6EhdB4RD8CP2xUxr2u3d6fos36PD98XS6oX8TQjLpsMwncs5DAMiD4nNnR8NBfyghGCWvCVifVwvA8B8TJxE1aiyiv2L429BCWfyzAme5sZW8rDb14NeCQHhZbtNqfXhcp2tAnaAT";
605        let params = vec![
606            json!(encoded_tx),
607            json!({
608                "encoding": "base58",
609            }),
610        ];
611        assert_eq!(
612            SendTransactionRequest::from_array_params(&params)
613                .expect("failed to parse a transaction from params"),
614            SendTransactionRequest {
615                transaction: encoded_tx.to_string(),
616                config: SendTransactionConfig::new(TransactionEncoding::Base58),
617            }
618        );
619    }
620
621    #[test]
622    fn test_send_transaction_from_array_too_many_params() {
623        let encoded_tx = "4hXTCkRzt9WyecNzV1XPgCDfGAZzQKNxLXgynz5QDuWWPSAZBZSHptvWRL3BjCvzUXRdKvHL2b7yGrRQcWyaqsaBCncVG7BFggS8w9snUts67BSh3EqKpXLUm5UMHfD7ZBe9GhARjbNQMLJ1QD3Spr6oMTBU6EhdB4RD8CP2xUxr2u3d6fos36PD98XS6oX8TQjLpsMwncs5DAMiD4nNnR8NBfyghGCWvCVifVwvA8B8TJxE1aiyiv2L429BCWfyzAme5sZW8rDb14NeCQHhZbtNqfXhcp2tAnaAT";
624        let params = vec![json!(encoded_tx), json!({}), json!({})];
625        SendTransactionRequest::from_array_params(&params).unwrap_err();
626    }
627
628    #[test]
629    fn test_from_array_params_get_block_basic() {
630        let params = vec![json!(123456)];
631
632        let request = GetBlockRequest::from_array_params(&params).unwrap();
633        assert_eq!(request.block_height, 123456);
634        assert!(request.config.is_none());
635    }
636
637    #[test]
638    fn test_from_array_params_get_block_with_config() {
639        let params = vec![
640            json!(123456),
641            json!({
642                "transactionDetails": "full"
643            }),
644        ];
645
646        let request = GetBlockRequest::from_array_params(&params).unwrap();
647        assert_eq!(request.block_height, 123456);
648        assert!(request.config.is_some());
649    }
650
651    #[test]
652    fn test_from_array_params_get_block_single_object() {
653        let params = vec![json!({
654            "blockHeight": 789012,
655            "config": {
656                "transactionDetails": "signatures"
657            }
658        })];
659
660        let request = GetBlockRequest::from_array_params(&params).unwrap();
661        assert_eq!(request.block_height, 789012);
662        assert!(request.config.is_some());
663    }
664
665    #[test]
666    #[should_panic(expected = "block height")]
667    fn test_from_array_params_get_block_empty_params() {
668        GetBlockRequest::from_array_params(&[]).unwrap();
669    }
670
671    #[test]
672    #[should_panic(expected = "Too many parameters")]
673    fn test_from_array_params_get_block_too_many_params() {
674        GetBlockRequest::from_array_params(&[json!(123456), json!({}), json!({})]).unwrap();
675    }
676
677    #[test]
678    #[should_panic(expected = "block height")]
679    fn test_from_array_params_get_block_invalid_height_type() {
680        GetBlockRequest::from_array_params(&[json!("not_a_number")]).unwrap();
681    }
682
683    #[test]
684    #[should_panic(expected = "config parameter")]
685    fn test_from_array_params_get_block_invalid_config() {
686        GetBlockRequest::from_array_params(&[
687            json!(123456),
688            json!("invalid_config"), // Should be an object, not a string
689        ])
690        .unwrap();
691    }
692
693    #[test]
694    #[should_panic(expected = "config parameter")]
695    fn test_from_array_params_get_block_null_config() {
696        GetBlockRequest::from_array_params(&[json!(123456), json!(null)]).unwrap();
697    }
698
699    #[test]
700    fn test_from_array_params_get_signature_statuses_basic() {
701        let signatures = vec![
702            "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW",
703            "4hXTCkRzt9WyecNzV1XPgCDfGAZzQKNxLXgynz5QDuWWPSAZBZSHptvWRL3BjCvzUXRdKvHL2b7yGrRQcWyaqsaB"
704        ];
705        let params = vec![json!(signatures)];
706
707        let request = GetSignatureStatusesRequest::from_array_params(&params).unwrap();
708        assert_eq!(request.signatures, signatures);
709        assert!(request.config.is_none());
710    }
711
712    #[test]
713    fn test_from_array_params_get_signature_statuses_with_config() {
714        let signatures = vec![
715            "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"
716        ];
717        let config = json!({"searchTransactionHistory": true});
718        let params = vec![json!(signatures), config.clone()];
719
720        let request = GetSignatureStatusesRequest::from_array_params(&params).unwrap();
721        assert_eq!(request.signatures, signatures);
722        assert!(request.config.is_some());
723    }
724
725    #[test]
726    fn test_from_array_params_get_signature_statuses_single_object() {
727        let signatures = vec![
728            "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"
729        ];
730        let params = vec![json!({
731            "signatures": signatures,
732            "config": {"searchTransactionHistory": false}
733        })];
734
735        let request = GetSignatureStatusesRequest::from_array_params(&params).unwrap();
736        assert_eq!(request.signatures, signatures);
737        assert!(request.config.is_some());
738    }
739
740    #[test]
741    fn test_from_array_params_get_signature_statuses_empty_signatures() {
742        let request = GetSignatureStatusesRequest::from_array_params(&[json!([])]).unwrap();
743        assert!(request.signatures.is_empty());
744        assert!(request.config.is_none());
745    }
746
747    #[test]
748    #[should_panic(expected = "Missing the signatures parameter")]
749    fn test_from_array_params_get_signature_statuses_empty_params() {
750        GetSignatureStatusesRequest::from_array_params(&[]).unwrap();
751    }
752
753    #[test]
754    #[should_panic(expected = "Too many parameters")]
755    fn test_from_array_params_get_signature_statuses_too_many_params() {
756        GetSignatureStatusesRequest::from_array_params(&[json!(["sig1"]), json!({}), json!({})])
757            .unwrap();
758    }
759
760    #[test]
761    #[should_panic(expected = "Failed to parse the signatures parameter")]
762    fn test_from_array_params_get_signature_statuses_invalid_signatures_type() {
763        GetSignatureStatusesRequest::from_array_params(&[json!("not an array")]).unwrap();
764    }
765
766    #[test]
767    #[should_panic(expected = "Failed to parse the config parameter")]
768    fn test_from_array_params_get_signature_statuses_invalid_config() {
769        GetSignatureStatusesRequest::from_array_params(&[
770            json!(["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]),
771            json!("invalid config")
772        ]).unwrap();
773    }
774
775    #[test]
776    fn test_from_array_params_get_signature_statuses_multiple_signatures() {
777        let signatures: Vec<&str> = (0..100).map(|i| {
778            if i % 2 == 0 {
779                "5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"
780            } else {
781                "4hXTCkRzt9WyecNzV1XPgCDfGAZzQKNxLXgynz5QDuWWPSAZBZSHptvWRL3BjCvzUXRdKvHL2b7yGrRQcWyaqsaB"
782            }
783        }).collect();
784        let params = vec![json!(signatures)];
785
786        let request = GetSignatureStatusesRequest::from_array_params(&params).unwrap();
787        assert_eq!(request.signatures.len(), 100);
788        assert!(request.config.is_none());
789    }
790
791    #[test]
792    #[should_panic(expected = "Failed to parse the config parameter")]
793    fn test_from_array_params_get_signature_statuses_null_config() {
794        GetSignatureStatusesRequest::from_array_params(&[
795            json!(["5VERv8NMvzbJMEkV8xnrLkEaWRtSz9CosKDYjCJjBRnbJLgp8uirBgmQpjKhoR4tjF3ZpRzrFmBV6UjKdiSZkQUW"]),
796            json!(null)]
797        ).unwrap();
798    }
799}