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