Skip to main content

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