Skip to main content

stellar_rpc_client/
lib.rs

1use http::{uri::Authority, Uri};
2use itertools::Itertools;
3use jsonrpsee_core::params::ObjectParams;
4use jsonrpsee_core::{self, client::ClientT};
5use jsonrpsee_http_client::{HeaderMap, HttpClient, HttpClientBuilder};
6use serde_aux::prelude::{
7    deserialize_default_from_null, deserialize_number_from_string,
8    deserialize_option_number_from_string,
9};
10use serde_with::{serde_as, DisplayFromStr};
11use stellar_xdr::{
12    self as xdr, AccountEntry, AccountId, ContractDataEntry, ContractEvent, ContractId,
13    DiagnosticEvent, Error as XdrError, Hash, LedgerCloseMeta, LedgerEntryData, LedgerFootprint,
14    LedgerHeaderHistoryEntry, LedgerKey, LedgerKeyAccount, Limited, Limits, PublicKey, ReadXdr,
15    ScContractInstance, SorobanAuthorizationEntry, SorobanResources, SorobanTransactionData,
16    TransactionEnvelope, TransactionEvent, TransactionMetaV3, TransactionResult, Uint256, VecM,
17    WriteXdr,
18};
19
20use std::{
21    f64::consts::E,
22    fmt::Display,
23    str::FromStr,
24    sync::Arc,
25    time::{Duration, Instant},
26};
27
28use termcolor::{Color, ColorChoice, StandardStream, WriteColor};
29use termcolor_output::colored;
30use tokio::time::sleep;
31
32const VERSION: Option<&str> = option_env!("CARGO_PKG_VERSION");
33
34/// Depth limit when encoding and decoding XDR.
35///
36/// 500 matches `soroban-env-host`'s `DEFAULT_XDR_RW_LIMITS`.
37const XDR_DEPTH_LIMIT: u32 = 500;
38
39pub type LogEvents = fn(
40    footprint: &LedgerFootprint,
41    auth: &[VecM<SorobanAuthorizationEntry>],
42    events: &[DiagnosticEvent],
43) -> ();
44
45pub type LogResources = fn(resources: &SorobanResources) -> ();
46
47#[derive(thiserror::Error, Debug)]
48#[allow(deprecated)] // Can be removed once Error enum doesn't have any code marked deprecated inside
49pub enum Error {
50    #[error(transparent)]
51    InvalidAddress(#[from] stellar_strkey::DecodeError),
52    #[error("invalid response from server")]
53    InvalidResponse,
54    #[error("provided network passphrase {expected:?} does not match the server: {server:?}")]
55    InvalidNetworkPassphrase { expected: String, server: String },
56    #[error("xdr processing error: {0}")]
57    Xdr(#[from] XdrError),
58    #[error("invalid rpc url: {0}")]
59    InvalidRpcUrl(http::uri::InvalidUri),
60    #[error("invalid rpc url: {0}")]
61    InvalidRpcUrlFromUriParts(http::uri::InvalidUriParts),
62    #[error("invalid friendbot url: {0}")]
63    InvalidUrl(String),
64    #[error(transparent)]
65    JsonRpc(#[from] jsonrpsee_core::ClientError),
66    #[error("json decoding error: {0}")]
67    Serde(#[from] serde_json::Error),
68    #[error("transaction failed: {0}")]
69    TransactionFailed(String),
70    #[error("transaction submission failed: {0}")]
71    TransactionSubmissionFailed(String),
72    #[error("expected transaction status: {0}")]
73    UnexpectedTransactionStatus(String),
74    #[error("transaction submission timeout")]
75    TransactionSubmissionTimeout,
76    #[error("transaction simulation failed: {0}")]
77    TransactionSimulationFailed(String),
78    #[error("{0} not found: {1}")]
79    NotFound(String, String),
80    #[error("Missing result in successful response")]
81    MissingResult,
82    #[error("Failed to read Error response from server")]
83    MissingError,
84    #[error("Missing signing key for account {address}")]
85    MissingSignerForAddress { address: String },
86    #[error("cursor is not valid")]
87    InvalidCursor,
88    #[error("unexpected ({length}) simulate transaction result length")]
89    UnexpectedSimulateTransactionResultSize { length: usize },
90    #[error("unexpected ({count}) number of operations")]
91    UnexpectedOperationCount { count: usize },
92    #[error("Transaction contains unsupported operation type")]
93    UnsupportedOperationType,
94    #[error("unexpected contract code data type: {0:?}")]
95    UnexpectedContractCodeDataType(LedgerEntryData),
96    #[error("unexpected contract instance type: {0:?}")]
97    UnexpectedContractInstance(xdr::ScVal),
98    #[error("unexpected contract code got token {0:?}")]
99    #[deprecated(note = "To be removed in future versions")]
100    UnexpectedToken(ContractDataEntry),
101    #[error("Fee was too large {0}")]
102    LargeFee(u64),
103    #[error("Cannot authorize raw transactions")]
104    CannotAuthorizeRawTransaction,
105    #[error("Missing result for tnx")]
106    MissingOp,
107}
108
109#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
110pub struct SendTransactionResponse {
111    pub hash: String,
112    pub status: String,
113    #[serde(
114        rename = "errorResultXdr",
115        skip_serializing_if = "Option::is_none",
116        default
117    )]
118    pub error_result_xdr: Option<String>,
119    #[serde(rename = "latestLedger")]
120    pub latest_ledger: u32,
121    #[serde(
122        rename = "latestLedgerCloseTime",
123        deserialize_with = "deserialize_number_from_string"
124    )]
125    pub latest_ledger_close_time: u32,
126}
127
128#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
129// TODO: add ledger info and application order
130pub struct GetTransactionResponseRaw {
131    pub status: String,
132
133    #[serde(skip_serializing_if = "Option::is_none")]
134    pub ledger: Option<u32>,
135
136    #[serde(
137        rename = "applicationOrder",
138        skip_serializing_if = "Option::is_none",
139        default
140    )]
141    pub application_order: Option<u32>,
142
143    #[serde(rename = "feeBump", skip_serializing_if = "Option::is_none", default)]
144    pub fee_bump: Option<bool>,
145
146    #[serde(
147        rename = "envelopeXdr",
148        skip_serializing_if = "Option::is_none",
149        default
150    )]
151    pub envelope_xdr: Option<String>,
152
153    #[serde(rename = "resultXdr", skip_serializing_if = "Option::is_none", default)]
154    pub result_xdr: Option<String>,
155
156    #[serde(
157        rename = "resultMetaXdr",
158        skip_serializing_if = "Option::is_none",
159        default
160    )]
161    pub result_meta_xdr: Option<String>,
162
163    #[serde(rename = "txHash", skip_serializing_if = "Option::is_none", default)]
164    pub tx_hash: Option<String>,
165
166    #[serde(
167        rename = "createdAt",
168        deserialize_with = "deserialize_option_i64_from_string_or_number",
169        skip_serializing_if = "Option::is_none",
170        default
171    )]
172    pub created_at: Option<i64>,
173
174    #[serde(rename = "events", skip_serializing_if = "Option::is_none", default)]
175    pub events: Option<GetTransactionEventsRaw>,
176
177    #[serde(
178        rename = "diagnosticEventsXdr",
179        skip_serializing_if = "Option::is_none",
180        default
181    )]
182    pub diagnostic_events_xdr: Option<Vec<String>>,
183}
184
185#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, Default)]
186pub struct GetTransactionEventsRaw {
187    #[serde(
188        rename = "contractEventsXdr",
189        skip_serializing_if = "Option::is_none",
190        default
191    )]
192    pub contract_events_xdr: Option<Vec<Vec<String>>>,
193
194    #[serde(
195        rename = "diagnosticEventsXdr",
196        skip_serializing_if = "Option::is_none",
197        default
198    )]
199    pub diagnostic_events_xdr: Option<Vec<String>>,
200
201    #[serde(
202        rename = "transactionEventsXdr",
203        skip_serializing_if = "Option::is_none",
204        default
205    )]
206    pub transaction_events_xdr: Option<Vec<String>>,
207}
208
209#[derive(serde::Deserialize, serde::Serialize, Clone, Debug)]
210pub struct GetTransactionEvents {
211    pub contract_events: Vec<Vec<ContractEvent>>,
212    pub diagnostic_events: Vec<DiagnosticEvent>,
213    pub transaction_events: Vec<TransactionEvent>,
214}
215
216#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
217pub struct GetTransactionResponse {
218    pub status: String,
219    pub ledger: Option<u32>,
220    pub application_order: Option<u32>,
221    pub fee_bump: Option<bool>,
222    pub tx_hash: Option<String>,
223    pub created_at: Option<i64>,
224    pub envelope: Option<xdr::TransactionEnvelope>,
225    pub result: Option<xdr::TransactionResult>,
226    pub result_meta: Option<xdr::TransactionMeta>,
227    pub events: GetTransactionEvents,
228}
229
230impl TryInto<GetTransactionResponse> for GetTransactionResponseRaw {
231    type Error = xdr::Error;
232
233    fn try_into(self) -> Result<GetTransactionResponse, Self::Error> {
234        let events = self.events.unwrap_or_default();
235        let result_meta: Option<xdr::TransactionMeta> = self
236            .result_meta_xdr
237            .map(|v| ReadXdr::from_xdr_base64(v, Limits::depth(XDR_DEPTH_LIMIT)))
238            .transpose()?;
239
240        // Failed transactions carry diagnostics in the top-level
241        // `diagnosticEventsXdr` field, while leaving the nested/meta path empty.
242        // Keep the raw strings here and only decode them in the fallback below,
243        // so successful transactions don't pay for parsing they'll discard.
244        let top_level_diagnostic_events_xdr = self.diagnostic_events_xdr.unwrap_or_default();
245
246        let mut events = match result_meta {
247            Some(xdr::TransactionMeta::V4(_)) => GetTransactionEvents {
248                contract_events: events
249                    .contract_events_xdr
250                    .unwrap_or_default()
251                    .into_iter()
252                    .map(|es| {
253                        es.into_iter()
254                            .filter_map(|e| {
255                                ContractEvent::from_xdr_base64(e, Limits::depth(XDR_DEPTH_LIMIT))
256                                    .ok()
257                            })
258                            .collect::<Vec<_>>()
259                    })
260                    .collect::<Vec<Vec<ContractEvent>>>(),
261
262                diagnostic_events: events
263                    .diagnostic_events_xdr
264                    .unwrap_or_default()
265                    .iter()
266                    .filter_map(|e| {
267                        DiagnosticEvent::from_xdr_base64(e, Limits::depth(XDR_DEPTH_LIMIT)).ok()
268                    })
269                    .collect(),
270
271                transaction_events: events
272                    .transaction_events_xdr
273                    .unwrap_or_default()
274                    .iter()
275                    .filter_map(|e| {
276                        TransactionEvent::from_xdr_base64(e, Limits::depth(XDR_DEPTH_LIMIT)).ok()
277                    })
278                    .collect(),
279            },
280
281            Some(xdr::TransactionMeta::V3(TransactionMetaV3 {
282                soroban_meta: Some(ref meta),
283                ..
284            })) => GetTransactionEvents {
285                contract_events: vec![],
286                transaction_events: vec![],
287                diagnostic_events: meta.diagnostic_events.clone().into(),
288            },
289
290            _ => GetTransactionEvents {
291                contract_events: vec![],
292                transaction_events: vec![],
293                diagnostic_events: vec![],
294            },
295        };
296
297        // Prefer the nested/meta diagnostics (populated for successful txs); fall
298        // back to the top-level field when they're absent (failed txs).
299        if events.diagnostic_events.is_empty() {
300            events.diagnostic_events = top_level_diagnostic_events_xdr
301                .iter()
302                .filter_map(|e| {
303                    DiagnosticEvent::from_xdr_base64(e, Limits::depth(XDR_DEPTH_LIMIT)).ok()
304                })
305                .collect();
306        }
307
308        Ok(GetTransactionResponse {
309            status: self.status,
310            ledger: self.ledger,
311            application_order: self.application_order,
312            fee_bump: self.fee_bump,
313            tx_hash: self.tx_hash,
314            created_at: self.created_at,
315            envelope: self
316                .envelope_xdr
317                .map(|v| ReadXdr::from_xdr_base64(v, Limits::depth(XDR_DEPTH_LIMIT)))
318                .transpose()?,
319            result: self
320                .result_xdr
321                .map(|v| ReadXdr::from_xdr_base64(v, Limits::depth(XDR_DEPTH_LIMIT)))
322                .transpose()?,
323            result_meta,
324            events,
325        })
326    }
327}
328
329impl GetTransactionResponse {
330    ///
331    /// # Errors
332    pub fn return_value(&self) -> Result<xdr::ScVal, Error> {
333        if let Some(xdr::TransactionMeta::V3(xdr::TransactionMetaV3 {
334            soroban_meta: Some(xdr::SorobanTransactionMeta { return_value, .. }),
335            ..
336        })) = &self.result_meta
337        {
338            return Ok(return_value.clone());
339        }
340
341        if let Some(xdr::TransactionMeta::V4(xdr::TransactionMetaV4 {
342            soroban_meta:
343                Some(xdr::SorobanTransactionMetaV2 {
344                    return_value: Some(return_value),
345                    ..
346                }),
347            ..
348        })) = &self.result_meta
349        {
350            return Ok(return_value.clone());
351        }
352
353        Err(Error::MissingOp)
354    }
355}
356
357#[serde_as]
358#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
359pub struct GetTransactionsResponseRaw {
360    pub transactions: Vec<GetTransactionResponseRaw>,
361    #[serde(rename = "latestLedger")]
362    pub latest_ledger: u32,
363    #[serde(rename = "latestLedgerCloseTimestamp")]
364    pub latest_ledger_close_time: i64,
365    #[serde(rename = "oldestLedger")]
366    pub oldest_ledger: u32,
367    #[serde(rename = "oldestLedgerCloseTimestamp")]
368    pub oldest_ledger_close_time: i64,
369    #[serde_as(as = "DisplayFromStr")]
370    pub cursor: u64,
371}
372
373#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
374pub struct GetTransactionsResponse {
375    pub transactions: Vec<GetTransactionResponse>,
376    pub latest_ledger: u32,
377    pub latest_ledger_close_time: i64,
378    pub oldest_ledger: u32,
379    pub oldest_ledger_close_time: i64,
380    pub cursor: u64,
381}
382
383impl TryInto<GetTransactionsResponse> for GetTransactionsResponseRaw {
384    type Error = xdr::Error; // assuming xdr::Error or any other error type that you use
385
386    fn try_into(self) -> Result<GetTransactionsResponse, Self::Error> {
387        Ok(GetTransactionsResponse {
388            transactions: self
389                .transactions
390                .into_iter()
391                .map(TryInto::try_into)
392                .collect::<Result<Vec<_>, xdr::Error>>()?,
393            latest_ledger: self.latest_ledger,
394            latest_ledger_close_time: self.latest_ledger_close_time,
395            oldest_ledger: self.oldest_ledger,
396            oldest_ledger_close_time: self.oldest_ledger_close_time,
397            cursor: self.cursor,
398        })
399    }
400}
401
402#[serde_as]
403#[derive(serde::Serialize, Debug, Clone)]
404pub struct TransactionsPaginationOptions {
405    #[serde_as(as = "Option<DisplayFromStr>")]
406    #[serde(skip_serializing_if = "Option::is_none")]
407    pub cursor: Option<u64>,
408    #[serde(skip_serializing_if = "Option::is_none")]
409    pub limit: Option<u32>,
410}
411
412#[derive(serde::Serialize, Debug, Clone)]
413pub struct GetTransactionsRequest {
414    #[serde(skip_serializing_if = "Option::is_none")]
415    pub start_ledger: Option<u32>,
416    pub pagination: Option<TransactionsPaginationOptions>,
417}
418
419#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
420pub struct LedgerEntryResult {
421    pub key: String,
422    pub xdr: String,
423    #[serde(rename = "lastModifiedLedgerSeq")]
424    pub last_modified_ledger: u32,
425    #[serde(
426        rename = "liveUntilLedgerSeq",
427        skip_serializing_if = "Option::is_none",
428        deserialize_with = "deserialize_option_number_from_string",
429        default
430    )]
431    pub live_until_ledger_seq_ledger_seq: Option<u32>,
432}
433
434#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
435pub struct GetLedgerEntriesResponse {
436    pub entries: Option<Vec<LedgerEntryResult>>,
437    #[serde(rename = "latestLedger")]
438    pub latest_ledger: i64,
439}
440
441#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
442pub struct GetNetworkResponse {
443    #[serde(
444        rename = "friendbotUrl",
445        skip_serializing_if = "Option::is_none",
446        default
447    )]
448    pub friendbot_url: Option<String>,
449    pub passphrase: String,
450    #[serde(rename = "protocolVersion")]
451    pub protocol_version: u32,
452}
453
454#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
455pub struct GetHealthResponse {
456    pub status: String,
457    #[serde(rename = "latestLedger")]
458    pub latest_ledger: u32,
459    #[serde(rename = "oldestLedger")]
460    pub oldest_ledger: u32,
461    #[serde(rename = "ledgerRetentionWindow")]
462    pub ledger_retention_window: u32,
463}
464
465#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
466pub struct GetVersionInfoResponse {
467    pub version: String,
468    #[serde(rename = "commitHash")]
469    pub commmit_hash: String,
470    #[serde(rename = "buildTimestamp")]
471    pub build_timestamp: String,
472    #[serde(rename = "captiveCoreVersion")]
473    pub captive_core_version: String,
474    #[serde(rename = "protocolVersion")]
475    pub protocol_version: u32,
476}
477
478#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
479pub struct GetLatestLedgerResponse {
480    pub id: String,
481    #[serde(rename = "protocolVersion")]
482    pub protocol_version: u32,
483    pub sequence: u32,
484}
485
486#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
487pub struct GetFeeStatsResponse {
488    #[serde(rename = "sorobanInclusionFee")]
489    pub soroban_inclusion_fee: FeeStat,
490    #[serde(rename = "inclusionFee")]
491    pub inclusion_fee: FeeStat,
492    #[serde(
493        rename = "latestLedger",
494        deserialize_with = "deserialize_number_from_string"
495    )]
496    pub latest_ledger: u32,
497}
498
499#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
500pub struct FeeStat {
501    pub max: String,
502    pub min: String,
503    // Fee value which occurs the most often
504    pub mode: String,
505    // 10th nearest-rank fee percentile
506    pub p10: String,
507    // 20th nearest-rank fee percentile
508    pub p20: String,
509    // 30th nearest-rank fee percentile
510    pub p30: String,
511    // 40th nearest-rank fee percentile
512    pub p40: String,
513    // 50th nearest-rank fee percentile
514    pub p50: String,
515    // 60th nearest-rank fee percentile
516    pub p60: String,
517    // 70th nearest-rank fee percentile
518    pub p70: String,
519    // 80th nearest-rank fee percentile
520    pub p80: String,
521    // 90th nearest-rank fee percentile.
522    pub p90: String,
523    // 95th nearest-rank fee percentile.
524    pub p95: String,
525    // 99th nearest-rank fee percentile
526    pub p99: String,
527    // How many transactions are part of the distribution
528    #[serde(
529        rename = "transactionCount",
530        deserialize_with = "deserialize_number_from_string"
531    )]
532    pub transaction_count: u32,
533    // How many consecutive ledgers form the distribution
534    #[serde(
535        rename = "ledgerCount",
536        deserialize_with = "deserialize_number_from_string"
537    )]
538    pub ledger_count: u32,
539}
540
541#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
542pub struct Cost {
543    #[serde(
544        rename = "cpuInsns",
545        deserialize_with = "deserialize_number_from_string"
546    )]
547    pub cpu_insns: u64,
548    #[serde(
549        rename = "memBytes",
550        deserialize_with = "deserialize_number_from_string"
551    )]
552    pub mem_bytes: u64,
553}
554
555#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
556pub struct SimulateHostFunctionResultRaw {
557    #[serde(deserialize_with = "deserialize_default_from_null")]
558    pub auth: Vec<String>,
559    pub xdr: String,
560}
561
562#[derive(Debug, Clone)]
563pub struct SimulateHostFunctionResult {
564    pub auth: Vec<SorobanAuthorizationEntry>,
565    pub xdr: xdr::ScVal,
566}
567
568#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq)]
569#[serde(tag = "type")]
570pub enum LedgerEntryChange {
571    #[serde(rename = "created")]
572    Created { key: String, after: String },
573    #[serde(rename = "deleted")]
574    Deleted { key: String, before: String },
575    #[serde(rename = "updated")]
576    Updated {
577        key: String,
578        before: String,
579        after: String,
580    },
581}
582
583#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
584pub struct SimulateTransactionResponse {
585    #[serde(
586        rename = "minResourceFee",
587        deserialize_with = "deserialize_number_from_string",
588        default
589    )]
590    pub min_resource_fee: u64,
591    #[serde(default)]
592    pub cost: Cost,
593    #[serde(skip_serializing_if = "Vec::is_empty", default)]
594    pub results: Vec<SimulateHostFunctionResultRaw>,
595    #[serde(rename = "transactionData", default)]
596    pub transaction_data: String,
597    #[serde(
598        deserialize_with = "deserialize_default_from_null",
599        skip_serializing_if = "Vec::is_empty",
600        default
601    )]
602    pub events: Vec<String>,
603    #[serde(
604        rename = "restorePreamble",
605        skip_serializing_if = "Option::is_none",
606        default
607    )]
608    pub restore_preamble: Option<RestorePreamble>,
609    #[serde(
610        rename = "stateChanges",
611        skip_serializing_if = "Option::is_none",
612        default
613    )]
614    pub state_changes: Option<Vec<LedgerEntryChange>>,
615    #[serde(rename = "latestLedger")]
616    pub latest_ledger: u32,
617    #[serde(skip_serializing_if = "Option::is_none", default)]
618    pub error: Option<String>,
619}
620
621impl SimulateTransactionResponse {
622    ///
623    /// # Errors
624    pub fn results(&self) -> Result<Vec<SimulateHostFunctionResult>, Error> {
625        self.results
626            .iter()
627            .map(|r| {
628                Ok(SimulateHostFunctionResult {
629                    auth: r
630                        .auth
631                        .iter()
632                        .map(|a| {
633                            Ok(SorobanAuthorizationEntry::from_xdr_base64(
634                                a,
635                                Limits::depth(XDR_DEPTH_LIMIT),
636                            )?)
637                        })
638                        .collect::<Result<_, Error>>()?,
639                    xdr: xdr::ScVal::from_xdr_base64(&r.xdr, Limits::depth(XDR_DEPTH_LIMIT))?,
640                })
641            })
642            .collect()
643    }
644
645    ///
646    /// # Errors
647    pub fn events(&self) -> Result<Vec<DiagnosticEvent>, Error> {
648        self.events
649            .iter()
650            .map(|e| {
651                Ok(DiagnosticEvent::from_xdr_base64(
652                    e,
653                    Limits::depth(XDR_DEPTH_LIMIT),
654                )?)
655            })
656            .collect()
657    }
658
659    ///
660    /// # Errors
661    pub fn transaction_data(&self) -> Result<SorobanTransactionData, Error> {
662        Ok(SorobanTransactionData::from_xdr_base64(
663            &self.transaction_data,
664            Limits::depth(XDR_DEPTH_LIMIT),
665        )?)
666    }
667}
668
669#[derive(serde::Deserialize, serde::Serialize, Debug, Default, Clone)]
670pub struct RestorePreamble {
671    #[serde(rename = "transactionData")]
672    pub transaction_data: String,
673    #[serde(
674        rename = "minResourceFee",
675        deserialize_with = "deserialize_number_from_string"
676    )]
677    pub min_resource_fee: u64,
678}
679
680#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
681pub struct GetEventsResponse {
682    #[serde(deserialize_with = "deserialize_default_from_null")]
683    pub events: Vec<Event>,
684    #[serde(rename = "latestLedger")]
685    pub latest_ledger: u32,
686    #[serde(rename = "latestLedgerCloseTime")]
687    pub latest_ledger_close_time: String,
688    #[serde(rename = "oldestLedger")]
689    pub oldest_ledger: u32,
690    #[serde(rename = "oldestLedgerCloseTime")]
691    pub oldest_ledger_close_time: String,
692    pub cursor: String,
693}
694
695#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
696pub struct GetLedgersResponse {
697    #[serde(rename = "latestLedger")]
698    pub latest_ledger: u32,
699    #[serde(
700        rename = "latestLedgerCloseTime",
701        deserialize_with = "deserialize_number_from_string"
702    )]
703    pub latest_ledger_close_time: i64,
704    #[serde(rename = "oldestLedger")]
705    pub oldest_ledger: u32,
706    #[serde(rename = "oldestLedgerCloseTime")]
707    pub oldest_ledger_close_time: i64,
708    pub cursor: String,
709    pub ledgers: Vec<Ledger>,
710}
711
712#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
713pub struct Ledger {
714    pub hash: String,
715    pub sequence: u32,
716    #[serde(rename = "ledgerCloseTime")]
717    pub ledger_close_time: String,
718    #[serde(rename = "headerXdr")]
719    pub header_xdr: String,
720    #[serde(rename = "headerJson")]
721    pub header_json: Option<LedgerHeaderHistoryEntry>,
722    #[serde(rename = "metadataXdr")]
723    pub metadata_xdr: String,
724    #[serde(rename = "metadataJson")]
725    pub metadata_json: Option<LedgerCloseMeta>,
726}
727
728#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
729pub struct Event {
730    #[serde(rename = "type")]
731    pub event_type: String,
732
733    pub ledger: u32,
734    #[serde(rename = "ledgerClosedAt")]
735    pub ledger_closed_at: String,
736    #[serde(rename = "contractId")]
737    pub contract_id: String,
738
739    pub id: String,
740
741    #[serde(
742        rename = "operationIndex",
743        default,
744        skip_serializing_if = "Option::is_none"
745    )]
746    pub operation_index: Option<u32>,
747    #[serde(
748        rename = "transactionIndex",
749        default,
750        skip_serializing_if = "Option::is_none"
751    )]
752    pub transaction_index: Option<u32>,
753    #[serde(rename = "txHash", default, skip_serializing_if = "Option::is_none")]
754    pub tx_hash: Option<String>,
755    #[deprecated(
756        note = "This field is deprecated by Stellar RPC. See https://stellar.org/blog/developers/protocol-23-upgrade-guide"
757    )]
758    #[serde(
759        rename = "inSuccessfulContractCall",
760        default,
761        skip_serializing_if = "Option::is_none"
762    )]
763    pub is_successful_contract_call: Option<bool>,
764
765    pub topic: Vec<String>,
766    pub value: String,
767}
768
769impl Display for Event {
770    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
771        writeln!(
772            f,
773            "Event {} [{}]:",
774            self.id,
775            self.event_type.to_ascii_uppercase()
776        )?;
777        writeln!(
778            f,
779            "  Ledger:   {} (closed at {})",
780            self.ledger, self.ledger_closed_at
781        )?;
782        writeln!(f, "  Contract: {}", self.contract_id)?;
783        writeln!(f, "  Topics:")?;
784
785        for topic in &self.topic {
786            let scval = xdr::ScVal::from_xdr_base64(topic, Limits::depth(XDR_DEPTH_LIMIT))
787                .map_err(|_| std::fmt::Error)?;
788            writeln!(f, "            {scval:?}")?;
789        }
790
791        let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::depth(XDR_DEPTH_LIMIT))
792            .map_err(|_| std::fmt::Error)?;
793
794        writeln!(f, "  Value:    {scval:?}")
795    }
796}
797
798pub type SegmentFilter = String;
799pub type TopicFilter = Vec<SegmentFilter>;
800
801impl Event {
802    ///
803    /// # Errors
804    pub fn parse_cursor(&self) -> Result<(u64, i32), Error> {
805        parse_cursor(&self.id)
806    }
807
808    ///
809    /// # Errors
810    pub fn pretty_print(&self) -> Result<(), Box<dyn std::error::Error>> {
811        let mut stdout = StandardStream::stdout(ColorChoice::Auto);
812
813        if !stdout.supports_color() {
814            println!("{self}");
815            return Ok(());
816        }
817
818        let color = match self.event_type.as_str() {
819            "system" => Color::Yellow,
820            _ => Color::Blue,
821        };
822        colored!(
823            stdout,
824            "{}Event{} {}{}{} [{}{}{}{}]:\n",
825            bold!(true),
826            bold!(false),
827            fg!(Some(Color::Green)),
828            self.id,
829            reset!(),
830            bold!(true),
831            fg!(Some(color)),
832            self.event_type.to_ascii_uppercase(),
833            reset!(),
834        )?;
835
836        colored!(
837            stdout,
838            "  Ledger:   {}{}{} (closed at {}{}{})\n",
839            fg!(Some(Color::Green)),
840            self.ledger,
841            reset!(),
842            fg!(Some(Color::Green)),
843            self.ledger_closed_at,
844            reset!(),
845        )?;
846
847        colored!(
848            stdout,
849            "  Contract: {}{}{}\n",
850            fg!(Some(Color::Green)),
851            self.contract_id,
852            reset!(),
853        )?;
854
855        colored!(stdout, "  Topics:\n")?;
856        for topic in &self.topic {
857            let scval = xdr::ScVal::from_xdr_base64(topic, Limits::depth(XDR_DEPTH_LIMIT))?;
858            colored!(
859                stdout,
860                "            {}{:?}{}\n",
861                fg!(Some(Color::Green)),
862                scval,
863                reset!(),
864            )?;
865        }
866
867        let scval = xdr::ScVal::from_xdr_base64(&self.value, Limits::depth(XDR_DEPTH_LIMIT))?;
868        colored!(
869            stdout,
870            "  Value: {}{:?}{}\n\n",
871            fg!(Some(Color::Green)),
872            scval,
873            reset!(),
874        )?;
875
876        Ok(())
877    }
878}
879
880/// Defines non-root authorization for simulated transactions.
881pub enum AuthMode {
882    Enforce,
883    Record,
884    RecordAllowNonRoot,
885}
886
887#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq, clap::ValueEnum)]
888pub enum EventType {
889    All,
890    Contract,
891    System,
892}
893
894#[derive(Clone, Debug, Eq, Hash, PartialEq)]
895pub enum LedgerStart {
896    Ledger(u32),
897    Cursor(String),
898}
899
900/// An inclusive ledger range. Construct via [`EventStart::ledger_range`].
901#[derive(Clone, Debug, Eq, Hash, PartialEq)]
902pub struct LedgerRange {
903    start: u32,
904    end: u32,
905}
906
907impl LedgerRange {
908    pub fn start(&self) -> u32 {
909        self.start
910    }
911
912    pub fn end(&self) -> u32 {
913        self.end
914    }
915}
916
917#[derive(Clone, Debug, Eq, Hash, PartialEq)]
918pub enum EventStart {
919    Ledger(u32),
920    /// A range of ledgers, inclusive. Use [`EventStart::ledger_range`] to
921    /// construct this variant with validation.
922    LedgerRange(LedgerRange),
923    Cursor(String),
924}
925
926impl EventStart {
927    /// Construct an [`EventStart::LedgerRange`] ensuring that `start <= end`.
928    ///
929    /// Returns an `Err` with a descriptive message if `start > end`.
930    pub fn ledger_range(start: u32, end: u32) -> Result<Self, String> {
931        if start > end {
932            return Err(format!(
933                "invalid ledger range: start ({start}) must be <= end ({end})"
934            ));
935        }
936        Ok(EventStart::LedgerRange(LedgerRange { start, end }))
937    }
938}
939
940#[derive(serde::Deserialize, serde::Serialize, Debug, Clone, PartialEq)]
941pub struct FullLedgerEntry {
942    pub key: LedgerKey,
943    pub val: LedgerEntryData,
944    #[serde(rename = "lastModifiedLedgerSeq")]
945    pub last_modified_ledger: u32,
946    #[serde(
947        rename = "liveUntilLedgerSeq",
948        skip_serializing_if = "Option::is_none",
949        deserialize_with = "deserialize_option_number_from_string",
950        default
951    )]
952    pub live_until_ledger_seq: Option<u32>,
953}
954
955#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
956pub struct FullLedgerEntries {
957    pub entries: Vec<FullLedgerEntry>,
958    #[serde(rename = "latestLedger")]
959    pub latest_ledger: i64,
960}
961
962#[derive(Debug, Clone)]
963pub struct Client {
964    base_url: Arc<str>,
965    timeout_in_secs: u64,
966    http_client: Arc<HttpClient>,
967}
968
969#[derive(serde::Deserialize, serde::Serialize, Debug, Clone)]
970/// Contains configuration for how resources will be calculated when simulating transactions.
971pub struct ResourceConfig {
972    /// Allow this many extra instructions when budgeting resources.
973    #[serde(rename = "instructionLeeway")]
974    pub instruction_leeway: u64,
975}
976
977#[allow(deprecated)] // Can be removed once Client doesn't have any code marked deprecated inside
978impl Client {
979    ///
980    /// # Errors
981    pub fn new(base_url: &str) -> Result<Self, Error> {
982        // Add the port to the base URL if there is no port explicitly included
983        // in the URL and the scheme allows us to infer a default port.
984        // Jsonrpsee requires a port to always be present even if one can be
985        // inferred. This may change: https://github.com/paritytech/jsonrpsee/issues/1048.
986        let uri = base_url.parse::<Uri>().map_err(Error::InvalidRpcUrl)?;
987        let mut parts = uri.into_parts();
988
989        if let (Some(scheme), Some(authority)) = (&parts.scheme, &parts.authority) {
990            if authority.port().is_none() {
991                let port = match scheme.as_str() {
992                    "http" => Some(80),
993                    "https" => Some(443),
994                    _ => None,
995                };
996                if let Some(port) = port {
997                    let host = authority.host();
998                    parts.authority = Some(
999                        Authority::from_str(&format!("{host}:{port}"))
1000                            .map_err(Error::InvalidRpcUrl)?,
1001                    );
1002                }
1003            }
1004        }
1005
1006        let uri = Uri::from_parts(parts).map_err(Error::InvalidRpcUrlFromUriParts)?;
1007        let base_url = Arc::from(uri.to_string());
1008        let headers = Self::default_http_headers();
1009        let http_client = Arc::new(
1010            HttpClientBuilder::default()
1011                .set_headers(headers)
1012                .build(&base_url)?,
1013        );
1014
1015        Ok(Self {
1016            base_url,
1017            timeout_in_secs: 30,
1018            http_client,
1019        })
1020    }
1021
1022    /// Create a new client with a timeout in seconds
1023    /// # Errors
1024    #[deprecated(
1025        note = "To be marked private in a future major release. Please use `new_with_headers` instead."
1026    )]
1027    pub fn new_with_timeout(base_url: &str, timeout: u64) -> Result<Self, Error> {
1028        let mut client = Self::new(base_url)?;
1029        client.timeout_in_secs = timeout;
1030        Ok(client)
1031    }
1032
1033    /// Create a new client with additional headers
1034    /// # Errors
1035    pub fn new_with_headers(base_url: &str, additional_headers: HeaderMap) -> Result<Self, Error> {
1036        let mut client = Self::new(base_url)?;
1037        let mut headers = Self::default_http_headers();
1038
1039        for (key, value) in additional_headers {
1040            headers.insert(key.ok_or(Error::InvalidResponse)?, value);
1041        }
1042
1043        let http_client = Arc::new(
1044            HttpClientBuilder::default()
1045                .set_headers(headers)
1046                .build(base_url)?,
1047        );
1048
1049        client.http_client = http_client;
1050        Ok(client)
1051    }
1052
1053    fn default_http_headers() -> HeaderMap {
1054        let mut headers = HeaderMap::new();
1055        headers.insert("X-Client-Name", unsafe {
1056            "rs-stellar-rpc-client".parse().unwrap_unchecked()
1057        });
1058        let version = VERSION.unwrap_or("devel");
1059        headers.insert("X-Client-Version", unsafe {
1060            version.parse().unwrap_unchecked()
1061        });
1062
1063        headers
1064    }
1065
1066    #[must_use]
1067    pub fn base_url(&self) -> &str {
1068        &self.base_url
1069    }
1070
1071    #[must_use]
1072    pub fn client(&self) -> &HttpClient {
1073        &self.http_client
1074    }
1075
1076    ///
1077    /// # Errors
1078    pub async fn friendbot_url(&self) -> Result<String, Error> {
1079        let network = self.get_network().await?;
1080        network.friendbot_url.ok_or_else(|| {
1081            Error::NotFound(
1082                "Friendbot".to_string(),
1083                "Friendbot is not available on this network".to_string(),
1084            )
1085        })
1086    }
1087    ///
1088    /// # Errors
1089    pub async fn verify_network_passphrase(&self, expected: Option<&str>) -> Result<String, Error> {
1090        let server = self.get_network().await?.passphrase;
1091
1092        if let Some(expected) = expected {
1093            if expected != server {
1094                return Err(Error::InvalidNetworkPassphrase {
1095                    expected: expected.to_string(),
1096                    server,
1097                });
1098            }
1099        }
1100
1101        Ok(server)
1102    }
1103
1104    ///
1105    /// # Errors
1106    pub async fn get_network(&self) -> Result<GetNetworkResponse, Error> {
1107        Ok(self
1108            .client()
1109            .request("getNetwork", ObjectParams::new())
1110            .await?)
1111    }
1112
1113    ///
1114    /// # Errors
1115    pub async fn get_health(&self) -> Result<GetHealthResponse, Error> {
1116        Ok(self
1117            .client()
1118            .request("getHealth", ObjectParams::new())
1119            .await?)
1120    }
1121
1122    ///
1123    /// # Errors
1124    pub async fn get_latest_ledger(&self) -> Result<GetLatestLedgerResponse, Error> {
1125        Ok(self
1126            .client()
1127            .request("getLatestLedger", ObjectParams::new())
1128            .await?)
1129    }
1130
1131    ///
1132    /// # Errors
1133    pub async fn get_ledgers(
1134        &self,
1135        start: LedgerStart,
1136        limit: Option<usize>,
1137        format: Option<String>,
1138    ) -> Result<GetLedgersResponse, Error> {
1139        let mut oparams = ObjectParams::new();
1140
1141        let mut pagination = serde_json::Map::new();
1142        if let Some(limit) = limit {
1143            pagination.insert("limit".to_string(), limit.into());
1144        }
1145
1146        match start {
1147            LedgerStart::Ledger(l) => oparams.insert("startLedger", l)?,
1148            LedgerStart::Cursor(c) => {
1149                pagination.insert("cursor".to_string(), c.into());
1150            }
1151        }
1152
1153        oparams.insert("pagination", pagination)?;
1154
1155        if let Some(f) = format {
1156            oparams.insert("xdrFormat", f)?;
1157        }
1158
1159        Ok(self.client().request("getLedgers", oparams).await?)
1160    }
1161
1162    ///
1163    /// # Errors
1164    pub async fn get_account(&self, address: &str) -> Result<AccountEntry, Error> {
1165        let key = LedgerKey::Account(LedgerKeyAccount {
1166            account_id: AccountId(PublicKey::PublicKeyTypeEd25519(Uint256(
1167                stellar_strkey::ed25519::PublicKey::from_string(address)?.0,
1168            ))),
1169        });
1170        let keys = Vec::from([key]);
1171        let response = self.get_ledger_entries(&keys).await?;
1172        let entries = response.entries.unwrap_or_default();
1173
1174        if entries.is_empty() {
1175            return Err(Error::NotFound("Account".to_string(), address.to_owned()));
1176        }
1177
1178        let ledger_entry = &entries[0];
1179        let mut read = Limited::new(ledger_entry.xdr.as_bytes(), Limits::depth(XDR_DEPTH_LIMIT));
1180
1181        if let LedgerEntryData::Account(entry) = LedgerEntryData::read_xdr_base64(&mut read)? {
1182            Ok(entry)
1183        } else {
1184            Err(Error::InvalidResponse)
1185        }
1186    }
1187
1188    /// Get network fee stats
1189    /// # Errors
1190    pub async fn get_fee_stats(&self) -> Result<GetFeeStatsResponse, Error> {
1191        Ok(self
1192            .client()
1193            .request("getFeeStats", ObjectParams::new())
1194            .await?)
1195    }
1196
1197    ///
1198    /// # Errors
1199    pub async fn get_version_info(&self) -> Result<GetVersionInfoResponse, Error> {
1200        Ok(self
1201            .client()
1202            .request("getVersionInfo", ObjectParams::new())
1203            .await?)
1204    }
1205
1206    /// Send a transaction to the network and get back the hash of the transaction.
1207    /// # Errors
1208    pub async fn send_transaction(&self, tx: &TransactionEnvelope) -> Result<Hash, Error> {
1209        let mut oparams = ObjectParams::new();
1210        oparams.insert(
1211            "transaction",
1212            tx.to_xdr_base64(Limits::depth(XDR_DEPTH_LIMIT))?,
1213        )?;
1214        let SendTransactionResponse {
1215            hash,
1216            error_result_xdr,
1217            status,
1218            ..
1219        } = self
1220            .client()
1221            .request("sendTransaction", oparams)
1222            .await
1223            .map_err(|err| {
1224                Error::TransactionSubmissionFailed(format!("No status yet:\n {err:#?}"))
1225            })?;
1226
1227        if status == "ERROR" {
1228            let error = error_result_xdr
1229                .ok_or(Error::MissingError)
1230                .and_then(|x| {
1231                    TransactionResult::read_xdr_base64(&mut Limited::new(
1232                        x.as_bytes(),
1233                        Limits::depth(XDR_DEPTH_LIMIT),
1234                    ))
1235                    .map_err(|_| Error::InvalidResponse)
1236                })
1237                .map(|r| r.result)?;
1238
1239            return Err(Error::TransactionSubmissionFailed(format!("{error:#?}")));
1240        }
1241
1242        Ok(Hash::from_str(&hash)?)
1243    }
1244
1245    ///
1246    /// # Errors
1247    pub async fn send_transaction_polling(
1248        &self,
1249        tx: &TransactionEnvelope,
1250    ) -> Result<GetTransactionResponse, Error> {
1251        let hash = self.send_transaction(tx).await?;
1252        self.get_transaction_polling(&hash, None).await
1253    }
1254
1255    ///
1256    /// # Errors
1257    pub async fn simulate_transaction_envelope(
1258        &self,
1259        tx: &TransactionEnvelope,
1260        auth_mode: Option<AuthMode>,
1261    ) -> Result<SimulateTransactionResponse, Error> {
1262        let base64_tx = tx.to_xdr_base64(Limits::depth(XDR_DEPTH_LIMIT))?;
1263        let mut params = ObjectParams::new();
1264
1265        params.insert("transaction", base64_tx)?;
1266
1267        match auth_mode {
1268            Some(AuthMode::Enforce) => {
1269                params.insert("authMode", "enforce")?;
1270            }
1271            Some(AuthMode::Record) => {
1272                params.insert("authMode", "record")?;
1273            }
1274            Some(AuthMode::RecordAllowNonRoot) => {
1275                params.insert("authMode", "record_allow_nonroot")?;
1276            }
1277            None => {}
1278        }
1279
1280        let sim_res = self.client().request("simulateTransaction", params).await?;
1281
1282        Ok(sim_res)
1283    }
1284
1285    /// Internal function, not to be used.
1286    /// # Errors
1287    pub async fn next_simulate_transaction_envelope(
1288        &self,
1289        tx: &TransactionEnvelope,
1290        auth_mode: Option<AuthMode>,
1291        resource_config: Option<ResourceConfig>,
1292    ) -> Result<SimulateTransactionResponse, Error> {
1293        let base64_tx = tx.to_xdr_base64(Limits::depth(XDR_DEPTH_LIMIT))?;
1294        let mut params = ObjectParams::new();
1295
1296        params.insert("transaction", base64_tx)?;
1297
1298        match auth_mode {
1299            Some(AuthMode::Enforce) => {
1300                params.insert("authMode", "enforce")?;
1301            }
1302            Some(AuthMode::Record) => {
1303                params.insert("authMode", "record")?;
1304            }
1305            Some(AuthMode::RecordAllowNonRoot) => {
1306                params.insert("authMode", "record_allow_nonroot")?;
1307            }
1308            None => {}
1309        }
1310
1311        if let Some(ref config) = resource_config {
1312            let mut resource_config_params = ObjectParams::new();
1313            resource_config_params.insert("instructionLeeway", config.instruction_leeway)?;
1314            params.insert("resourceConfig", resource_config)?;
1315        }
1316
1317        let sim_res = self.client().request("simulateTransaction", params).await?;
1318
1319        Ok(sim_res)
1320    }
1321
1322    ///
1323    /// # Errors
1324    pub async fn get_transaction(&self, tx_id: &Hash) -> Result<GetTransactionResponse, Error> {
1325        let mut oparams = ObjectParams::new();
1326        oparams.insert("hash", tx_id)?;
1327        let resp: GetTransactionResponseRaw =
1328            self.client().request("getTransaction", oparams).await?;
1329
1330        Ok(resp.try_into()?)
1331    }
1332
1333    ///
1334    /// # Errors
1335    pub async fn get_transactions(
1336        &self,
1337        request: GetTransactionsRequest,
1338    ) -> Result<GetTransactionsResponse, Error> {
1339        let mut oparams = ObjectParams::new();
1340
1341        if let Some(start_ledger) = request.start_ledger {
1342            oparams.insert("startLedger", start_ledger)?;
1343        }
1344
1345        if let Some(pagination_params) = request.pagination {
1346            let pagination = serde_json::json!(pagination_params);
1347            oparams.insert("pagination", pagination)?;
1348        }
1349
1350        let resp: GetTransactionsResponseRaw =
1351            self.client().request("getTransactions", oparams).await?;
1352
1353        Ok(resp.try_into()?)
1354    }
1355
1356    /// Poll the transaction status. Can provide a timeout in seconds, otherwise uses the default timeout.
1357    ///
1358    /// It uses exponential backoff with a base of 1 second and a maximum of 30 seconds.
1359    ///
1360    /// # Errors
1361    /// - `Error::TransactionSubmissionTimeout` if the transaction status is not found within the timeout
1362    /// - `Error::TransactionSubmissionFailed` if the transaction status is "FAILED"
1363    /// - `Error::UnexpectedTransactionStatus` if the transaction status is not one of "SUCCESS", "FAILED", or ``NOT_FOUND``
1364    /// - `json_rpsee` Errors
1365    pub async fn get_transaction_polling(
1366        &self,
1367        tx_id: &Hash,
1368        timeout_s: Option<Duration>,
1369    ) -> Result<GetTransactionResponse, Error> {
1370        // Poll the transaction status
1371        let start = Instant::now();
1372        let timeout = timeout_s.unwrap_or(Duration::from_secs(self.timeout_in_secs));
1373        // see https://tsapps.nist.gov/publication/get_pdf.cfm?pub_id=50731
1374        // Is optimimal exponent for expontial backoff
1375        let exponential_backoff: f64 = 1.0 / (1.0 - E.powf(-1.0));
1376        let mut sleep_time = Duration::from_secs(1);
1377        loop {
1378            let response = self.get_transaction(tx_id).await?;
1379            match response.status.as_str() {
1380                "SUCCESS" => return Ok(response),
1381
1382                "FAILED" => {
1383                    return Err(Error::TransactionSubmissionFailed(format!(
1384                        "{:#?}",
1385                        response.result
1386                    )))
1387                }
1388
1389                "NOT_FOUND" => (),
1390                _ => {
1391                    return Err(Error::UnexpectedTransactionStatus(response.status));
1392                }
1393            }
1394
1395            if start.elapsed() > timeout {
1396                return Err(Error::TransactionSubmissionTimeout);
1397            }
1398
1399            sleep(sleep_time).await;
1400            sleep_time = Duration::from_secs_f64(sleep_time.as_secs_f64() * exponential_backoff);
1401        }
1402    }
1403
1404    ///
1405    /// # Errors
1406    pub async fn get_ledger_entries(
1407        &self,
1408        keys: &[LedgerKey],
1409    ) -> Result<GetLedgerEntriesResponse, Error> {
1410        let mut base64_keys: Vec<String> = vec![];
1411
1412        for k in keys {
1413            let base64_result = k.to_xdr_base64(Limits::depth(XDR_DEPTH_LIMIT));
1414            if base64_result.is_err() {
1415                return Err(Error::Xdr(XdrError::Invalid));
1416            }
1417            base64_keys.push(k.to_xdr_base64(Limits::depth(XDR_DEPTH_LIMIT))?);
1418        }
1419
1420        let mut oparams = ObjectParams::new();
1421        oparams.insert("keys", base64_keys)?;
1422
1423        Ok(self.client().request("getLedgerEntries", oparams).await?)
1424    }
1425
1426    ///
1427    /// # Errors
1428    pub async fn get_full_ledger_entries(
1429        &self,
1430        ledger_keys: &[LedgerKey],
1431    ) -> Result<FullLedgerEntries, Error> {
1432        let keys = ledger_keys
1433            .iter()
1434            .filter(|key| !matches!(key, LedgerKey::Ttl(_)))
1435            .map(Clone::clone)
1436            .collect::<Vec<_>>();
1437        let GetLedgerEntriesResponse {
1438            entries,
1439            latest_ledger,
1440        } = self.get_ledger_entries(&keys).await?;
1441        let entries = entries
1442            .unwrap_or_default()
1443            .iter()
1444            .map(
1445                |LedgerEntryResult {
1446                     key,
1447                     xdr,
1448                     last_modified_ledger,
1449                     live_until_ledger_seq_ledger_seq,
1450                 }| {
1451                    Ok(FullLedgerEntry {
1452                        key: LedgerKey::from_xdr_base64(key, Limits::depth(XDR_DEPTH_LIMIT))?,
1453                        val: LedgerEntryData::from_xdr_base64(xdr, Limits::depth(XDR_DEPTH_LIMIT))?,
1454                        live_until_ledger_seq: *live_until_ledger_seq_ledger_seq,
1455                        last_modified_ledger: *last_modified_ledger,
1456                    })
1457                },
1458            )
1459            .collect::<Result<Vec<_>, Error>>()?;
1460        Ok(FullLedgerEntries {
1461            entries,
1462            latest_ledger,
1463        })
1464    }
1465
1466    ///
1467    /// # Errors
1468    pub async fn get_events(
1469        &self,
1470        start: EventStart,
1471        event_type: Option<EventType>,
1472        contract_ids: &[String],
1473        topics: &[TopicFilter],
1474        limit: Option<usize>,
1475    ) -> Result<GetEventsResponse, Error> {
1476        let mut filters = serde_json::Map::new();
1477
1478        event_type
1479            .and_then(|t| match t {
1480                EventType::All => None, // all is the default, so avoid incl. the param
1481                EventType::Contract => Some("contract"),
1482                EventType::System => Some("system"),
1483            })
1484            .map(|t| filters.insert("type".to_string(), t.into()));
1485
1486        filters.insert("topics".to_string(), topics.into());
1487        filters.insert("contractIds".to_string(), contract_ids.into());
1488
1489        let mut pagination = serde_json::Map::new();
1490        if let Some(limit) = limit {
1491            pagination.insert("limit".to_string(), limit.into());
1492        }
1493
1494        let mut oparams = ObjectParams::new();
1495        match start {
1496            EventStart::Ledger(l) => oparams.insert("startLedger", l)?,
1497            EventStart::LedgerRange(r) => {
1498                oparams.insert("startLedger", r.start())?;
1499                oparams.insert("endLedger", r.end())?;
1500            }
1501            EventStart::Cursor(c) => {
1502                pagination.insert("cursor".to_string(), c.into());
1503            }
1504        }
1505        oparams.insert("filters", vec![filters])?;
1506        oparams.insert("pagination", pagination)?;
1507
1508        Ok(self.client().request("getEvents", oparams).await?)
1509    }
1510
1511    ///
1512    /// # Errors
1513    pub async fn get_contract_data(
1514        &self,
1515        contract_id: &[u8; 32],
1516    ) -> Result<ContractDataEntry, Error> {
1517        // Get the contract from the network
1518        let contract_key = LedgerKey::ContractData(xdr::LedgerKeyContractData {
1519            contract: xdr::ScAddress::Contract(ContractId(xdr::Hash(*contract_id))),
1520            key: xdr::ScVal::LedgerKeyContractInstance,
1521            durability: xdr::ContractDataDurability::Persistent,
1522        });
1523        let contract_ref = self.get_ledger_entries(&[contract_key]).await?;
1524        let entries = contract_ref.entries.unwrap_or_default();
1525        if entries.is_empty() {
1526            let contract_address = stellar_strkey::Contract(*contract_id).to_string();
1527            return Err(Error::NotFound(
1528                "Contract".to_string(),
1529                contract_address.to_string(),
1530            ));
1531        }
1532        let contract_ref_entry = &entries[0];
1533        match LedgerEntryData::from_xdr_base64(
1534            &contract_ref_entry.xdr,
1535            Limits::depth(XDR_DEPTH_LIMIT),
1536        )? {
1537            LedgerEntryData::ContractData(contract_data) => Ok(contract_data),
1538            scval => Err(Error::UnexpectedContractCodeDataType(scval)),
1539        }
1540    }
1541
1542    ///
1543    /// # Errors
1544    #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1545    pub async fn get_remote_wasm(&self, contract_id: &[u8; 32]) -> Result<Vec<u8>, Error> {
1546        match self.get_contract_data(contract_id).await? {
1547            xdr::ContractDataEntry {
1548                val:
1549                    xdr::ScVal::ContractInstance(xdr::ScContractInstance {
1550                        executable: xdr::ContractExecutable::Wasm(hash),
1551                        ..
1552                    }),
1553                ..
1554            } => self.get_remote_wasm_from_hash(hash).await,
1555            scval => Err(Error::UnexpectedToken(scval)),
1556        }
1557    }
1558
1559    ///
1560    /// # Errors
1561    #[deprecated(note = "To be removed in future versions, use get_ledger_entries()")]
1562    pub async fn get_remote_wasm_from_hash(&self, hash: Hash) -> Result<Vec<u8>, Error> {
1563        let code_key = LedgerKey::ContractCode(xdr::LedgerKeyContractCode { hash: hash.clone() });
1564        let contract_data = self.get_ledger_entries(&[code_key]).await?;
1565        let entries = contract_data.entries.unwrap_or_default();
1566        if entries.is_empty() {
1567            return Err(Error::NotFound(
1568                "Contract Code".to_string(),
1569                hex::encode(hash),
1570            ));
1571        }
1572        let contract_data_entry = &entries[0];
1573        match LedgerEntryData::from_xdr_base64(
1574            &contract_data_entry.xdr,
1575            Limits::depth(XDR_DEPTH_LIMIT),
1576        )? {
1577            LedgerEntryData::ContractCode(xdr::ContractCodeEntry { code, .. }) => Ok(code.into()),
1578            scval => Err(Error::UnexpectedContractCodeDataType(scval)),
1579        }
1580    }
1581
1582    /// Get the contract instance from the network. Could be normal contract or native Stellar Asset Contract (SAC)
1583    ///
1584    /// # Errors
1585    /// - Could fail to find contract or have a network error
1586    pub async fn get_contract_instance(
1587        &self,
1588        contract_id: &[u8; 32],
1589    ) -> Result<ScContractInstance, Error> {
1590        let contract_data = self.get_contract_data(contract_id).await?;
1591        match contract_data.val {
1592            xdr::ScVal::ContractInstance(instance) => Ok(instance),
1593            scval => Err(Error::UnexpectedContractInstance(scval)),
1594        }
1595    }
1596}
1597
1598pub(crate) fn parse_cursor(c: &str) -> Result<(u64, i32), Error> {
1599    let (toid_part, event_index) = c.split('-').collect_tuple().ok_or(Error::InvalidCursor)?;
1600    let toid_part: u64 = toid_part.parse().map_err(|_| Error::InvalidCursor)?;
1601    let start_index: i32 = event_index.parse().map_err(|_| Error::InvalidCursor)?;
1602    Ok((toid_part, start_index))
1603}
1604
1605fn deserialize_option_i64_from_string_or_number<'de, D>(
1606    deserializer: D,
1607) -> Result<Option<i64>, D::Error>
1608where
1609    D: serde::Deserializer<'de>,
1610{
1611    use serde::Deserialize;
1612
1613    #[derive(Deserialize)]
1614    #[serde(untagged)]
1615    enum StringOrNumber {
1616        String(String),
1617        Number(i64),
1618    }
1619
1620    match Option::<StringOrNumber>::deserialize(deserializer)? {
1621        None => Ok(None),
1622        Some(StringOrNumber::String(s)) => {
1623            s.parse::<i64>().map(Some).map_err(serde::de::Error::custom)
1624        }
1625        Some(StringOrNumber::Number(n)) => Ok(Some(n)),
1626    }
1627}
1628
1629#[cfg(test)]
1630mod tests {
1631    use super::*;
1632    use std::env;
1633    use std::fs;
1634    use std::path::PathBuf;
1635
1636    // Determines whether or not a particular filter matches a topic based on
1637    // the same semantics as the RPC server:
1638    //
1639    //  - for an exact segment match, the filter is a base64-encoded ScVal
1640    //  - for a wildcard, single-segment match, the string "*" matches exactly
1641    //    one segment
1642    //  - for a wildcard, multi-segment match, the string "**" as the last
1643    //    element of the filter matches zero or more trailing segments
1644    //
1645    // [API Reference](https://docs.google.com/document/d/1TZUDgo_3zPz7TiPMMHVW_mtogjLyPL0plvzGMsxSz6A/edit#bookmark=id.35t97rnag3tx)
1646    // [Code Reference](https://github.com/stellar/soroban-tools/blob/bac1be79e8c2590c9c35ad8a0168aab0ae2b4171/cmd/soroban-rpc/internal/methods/get_events.go#L182-L203)
1647    fn does_topic_match(topic: &[String], filter: &[String]) -> bool {
1648        if filter.is_empty() {
1649            return false;
1650        }
1651
1652        // "**" as the last filter element matches zero or more trailing segments.
1653        if let Some((last, prefix)) = filter.split_last() {
1654            if last == "**" {
1655                return topic.len() >= prefix.len()
1656                    && prefix
1657                        .iter()
1658                        .enumerate()
1659                        .all(|(i, s)| *s == "*" || topic[i] == *s);
1660            }
1661        }
1662
1663        filter.len() == topic.len()
1664            && filter
1665                .iter()
1666                .enumerate()
1667                .all(|(i, s)| *s == "*" || topic[i] == *s)
1668    }
1669
1670    fn get_repo_root() -> PathBuf {
1671        let mut path = env::current_exe().expect("Failed to get current executable path");
1672        // Navigate up the directory tree until we find the repository root
1673        while path.pop() {
1674            if path.join("Cargo.toml").exists() {
1675                return path;
1676            }
1677        }
1678        panic!("Could not find repository root");
1679    }
1680
1681    fn read_json_file(name: &str) -> String {
1682        let repo_root = get_repo_root();
1683        let fixture_path = repo_root.join("src").join("fixtures").join(name);
1684        fs::read_to_string(fixture_path).expect(&format!("Failed to read {name:?}"))
1685    }
1686
1687    #[test]
1688    fn simulation_transaction_response_parsing() {
1689        let s = r#"{
1690 "minResourceFee": "100000000",
1691 "cost": { "cpuInsns": "1000", "memBytes": "1000" },
1692 "transactionData": "",
1693 "latestLedger": 1234,
1694 "stateChanges": [{
1695    "type": "created",
1696    "key": "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==",
1697    "before": null,
1698    "after": "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA="
1699  }]
1700  }"#;
1701
1702        let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1703        assert_eq!(
1704            resp.state_changes.unwrap()[0],
1705            LedgerEntryChange::Created { key: "AAAAAAAAAABuaCbVXZ2DlXWarV6UxwbW3GNJgpn3ASChIFp5bxSIWg==".to_string(), after: "AAAAZAAAAAAAAAAAbmgm1V2dg5V1mq1elMcG1txjSYKZ9wEgoSBaeW8UiFoAAAAAAAAAZAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=".to_string() },
1706        );
1707        assert_eq!(resp.min_resource_fee, 100_000_000);
1708    }
1709
1710    #[test]
1711    fn simulation_transaction_response_parsing_mostly_empty() {
1712        let s = r#"{
1713 "latestLedger": 1234
1714        }"#;
1715
1716        let resp: SimulateTransactionResponse = serde_json::from_str(s).unwrap();
1717        assert_eq!(resp.latest_ledger, 1_234);
1718    }
1719
1720    #[test]
1721    fn test_parse_transaction_response_p23() {
1722        let response_content = read_json_file("transaction_response_p23.json");
1723        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1724            .expect("Failed to parse JSON from transaction_response_p23.json");
1725        let result = full_response["result"].clone();
1726        let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1727            .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1728        let response: GetTransactionResponse = raw_response
1729            .try_into()
1730            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1731
1732        assert_eq!(2, response.events.transaction_events.iter().len());
1733        assert_eq!(1, response.events.contract_events.len());
1734        assert_eq!(21, response.events.diagnostic_events.iter().len());
1735        assert_eq!(
1736            response.tx_hash.as_deref(),
1737            Some("bfe15f83ea850b7bf86fd7152f9074033f2aec2a045e40a8872ac56726a6e35c")
1738        );
1739        assert_eq!(response.created_at, Some(1_751_666_924));
1740        assert_eq!(response.application_order, Some(1));
1741        assert_eq!(response.fee_bump, Some(false));
1742    }
1743
1744    #[test]
1745    fn test_parse_transaction_response_p22() {
1746        let response_content = read_json_file("transaction_response_p22.json");
1747        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1748            .expect("Failed to parse JSON from transaction_response_p22.json");
1749        let result = full_response["result"].clone();
1750        let raw_response: GetTransactionResponseRaw = serde_json::from_value(result)
1751            .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1752        let response: GetTransactionResponse = raw_response
1753            .try_into()
1754            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1755
1756        assert_eq!(23, response.events.diagnostic_events.iter().len());
1757        assert_eq!(
1758            response.tx_hash.as_deref(),
1759            Some("a738ccc7f8f457d4367b78c098569ebee23258c71f128d7a2c61585652345937")
1760        );
1761        assert_eq!(response.created_at, Some(1_751_747_980));
1762        assert_eq!(response.application_order, Some(1));
1763        assert_eq!(response.fee_bump, Some(false));
1764    }
1765
1766    #[test]
1767    fn test_parse_failed_transaction_recovers_top_level_diagnostic_events() {
1768        let response_content = read_json_file("transaction_response_failed_p23.json");
1769        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1770            .expect("Failed to parse JSON from transaction_response_failed_p23.json");
1771        let raw_response: GetTransactionResponseRaw =
1772            serde_json::from_value(full_response["result"].clone())
1773                .expect("Failed to parse 'result' into GetTransactionResponseRaw");
1774        let response: GetTransactionResponse = raw_response
1775            .try_into()
1776            .expect("Failed to convert GetTransactionResponseRaw to GetTransactionResponse");
1777
1778        assert_eq!(response.status, "FAILED");
1779        // Diagnostics live only in the top-level `diagnosticEventsXdr` field for
1780        // failed txs; the nested `events.diagnosticEventsXdr` is absent.
1781        assert_eq!(response.events.diagnostic_events.len(), 21);
1782    }
1783
1784    #[test]
1785    fn test_parse_get_transactions_response() {
1786        let response_content = read_json_file("transactions_response.json");
1787
1788        // Parse the entire response
1789        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1790            .expect("Failed to parse JSON from transactions_response.json");
1791
1792        // Extract the "result" field
1793        let result = full_response["result"].clone();
1794        // Parse the "result" content as GetTransactionsResponseRaw
1795        let raw_response: GetTransactionsResponseRaw = serde_json::from_value(result)
1796            .expect("Failed to parse 'result' into GetTransactionsResponseRaw");
1797
1798        // Convert GetTransactionsResponseRaw to GetTransactionsResponse
1799        let response: GetTransactionsResponse = raw_response
1800            .try_into()
1801            .expect("Failed to convert GetTransactionsResponseRaw to GetTransactionsResponse");
1802
1803        // Assertions
1804        assert_eq!(response.transactions.len(), 5);
1805        assert_eq!(response.latest_ledger, 556_962);
1806        assert_eq!(response.cursor, 2_379_420_471_922_689);
1807
1808        // Additional assertions for specific transaction attributes
1809        assert_eq!(response.transactions[0].status, "SUCCESS");
1810        //assert_eq!(response.transactions[0].application_order, 1);
1811        //assert_eq!(response.transactions[0].ledger, 554000);
1812    }
1813
1814    #[test]
1815    fn test_rpc_url_default_ports() {
1816        // Default ports are added.
1817        let client = Client::new("http://example.com").unwrap();
1818        assert_eq!(client.base_url(), "http://example.com:80/");
1819        let client = Client::new("https://example.com").unwrap();
1820        assert_eq!(client.base_url(), "https://example.com:443/");
1821
1822        // Ports are not added when already present.
1823        let client = Client::new("http://example.com:8080").unwrap();
1824        assert_eq!(client.base_url(), "http://example.com:8080/");
1825        let client = Client::new("https://example.com:8080").unwrap();
1826        assert_eq!(client.base_url(), "https://example.com:8080/");
1827
1828        // Paths are not modified.
1829        let client = Client::new("http://example.com/a/b/c").unwrap();
1830        assert_eq!(client.base_url(), "http://example.com:80/a/b/c");
1831        let client = Client::new("https://example.com/a/b/c").unwrap();
1832        assert_eq!(client.base_url(), "https://example.com:443/a/b/c");
1833        let client = Client::new("http://example.com/a/b/c/").unwrap();
1834        assert_eq!(client.base_url(), "http://example.com:80/a/b/c/");
1835        let client = Client::new("https://example.com/a/b/c/").unwrap();
1836        assert_eq!(client.base_url(), "https://example.com:443/a/b/c/");
1837        let client = Client::new("http://example.com/a/b:80/c/").unwrap();
1838        assert_eq!(client.base_url(), "http://example.com:80/a/b:80/c/");
1839        let client = Client::new("https://example.com/a/b:80/c/").unwrap();
1840        assert_eq!(client.base_url(), "https://example.com:443/a/b:80/c/");
1841    }
1842
1843    #[test]
1844    fn test_parse_events_response() {
1845        let response_content = read_json_file("events_response_p23.json");
1846        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1847            .expect("Failed to parse JSON from events_response_p23.json");
1848        let result = full_response["result"].clone();
1849
1850        // Deserialize
1851        let resp: GetEventsResponse = serde_json::from_value(result.clone())
1852            .expect("Failed to parse 'result' into GetEventsResponse");
1853
1854        // Verify specific field values from the fixture.
1855        assert_eq!(resp.events[0].operation_index, Some(0));
1856        assert_eq!(resp.events[0].transaction_index, Some(0));
1857        assert_eq!(
1858            resp.events[0].tx_hash.as_deref(),
1859            Some("e42da3c70c90cc319e2cfaa2f69a7bd04aefcc4159b12caa0df216fbb3ab43b4")
1860        );
1861        #[allow(deprecated)]
1862        {
1863            assert_eq!(resp.events[0].is_successful_contract_call, Some(true));
1864        }
1865
1866        // Re-serialize
1867        let reserialized = serde_json::to_value(&resp).expect("Failed to serialize response");
1868
1869        // Compare
1870        assert_eq!(
1871            result, reserialized,
1872            "Deserialization should preserve all data"
1873        );
1874    }
1875
1876    #[test]
1877    fn test_parse_events_response_p22() {
1878        // Ensure we can still deserialize Event from protocol 22 responses,
1879        // which do not include operationIndex or transactionIndex.
1880        let response_content = read_json_file("events_response_p22.json");
1881        let full_response: serde_json::Value = serde_json::from_str(&response_content)
1882            .expect("Failed to parse JSON from events_response_p22.json");
1883        let first_event = full_response["result"]["events"][0].clone();
1884
1885        // Deserialize; this should succeed even though some fields are absent.
1886        let event: Event = serde_json::from_value(first_event)
1887            .expect("Failed to parse protocol 22 event into Event");
1888
1889        assert!(event.operation_index.is_none());
1890        assert!(event.transaction_index.is_none());
1891    }
1892
1893    #[test]
1894    fn test_ledger_range_valid() {
1895        let r = EventStart::ledger_range(10, 20).unwrap();
1896        assert_eq!(r, EventStart::ledger_range(10, 20).unwrap());
1897
1898        // equal start and end is valid
1899        assert!(EventStart::ledger_range(10, 10).is_ok());
1900    }
1901
1902    #[test]
1903    fn test_ledger_range_invalid() {
1904        let err = EventStart::ledger_range(100, 50).unwrap_err();
1905        assert!(err.contains("start (100)") && err.contains("end (50)"));
1906    }
1907
1908    #[test]
1909    // Taken from [RPC server
1910    // tests](https://github.com/stellar/soroban-tools/blob/main/cmd/soroban-rpc/internal/methods/get_events_test.go#L21).
1911    fn test_does_topic_match() {
1912        struct TestCase<'a> {
1913            name: &'a str,
1914            filter: Vec<&'a str>,
1915            includes: Vec<Vec<&'a str>>,
1916            excludes: Vec<Vec<&'a str>>,
1917        }
1918
1919        let xfer = "AAAABQAAAAh0cmFuc2Zlcg==";
1920        let number = "AAAAAQB6Mcc=";
1921        let star = "*";
1922
1923        for tc in vec![
1924            // No filter means match nothing.
1925            TestCase {
1926                name: "<empty>",
1927                filter: vec![],
1928                includes: vec![],
1929                excludes: vec![vec![xfer]],
1930            },
1931            // "*" should match "transfer/" but not "transfer/transfer" or
1932            // "transfer/amount", because * is specified as a SINGLE segment
1933            // wildcard.
1934            TestCase {
1935                name: "*",
1936                filter: vec![star],
1937                includes: vec![vec![xfer]],
1938                excludes: vec![vec![xfer, xfer], vec![xfer, number]],
1939            },
1940            // "*/transfer" should match anything preceding "transfer", but
1941            // nothing that isn't exactly two segments long.
1942            TestCase {
1943                name: "*/transfer",
1944                filter: vec![star, xfer],
1945                includes: vec![vec![number, xfer], vec![xfer, xfer]],
1946                excludes: vec![
1947                    vec![number],
1948                    vec![number, number],
1949                    vec![number, xfer, number],
1950                    vec![xfer],
1951                    vec![xfer, number],
1952                    vec![xfer, xfer, xfer],
1953                ],
1954            },
1955            // The inverse case of before: "transfer/*" should match any single
1956            // segment after a segment that is exactly "transfer", but no
1957            // additional segments.
1958            TestCase {
1959                name: "transfer/*",
1960                filter: vec![xfer, star],
1961                includes: vec![vec![xfer, number], vec![xfer, xfer]],
1962                excludes: vec![
1963                    vec![number],
1964                    vec![number, number],
1965                    vec![number, xfer, number],
1966                    vec![xfer],
1967                    vec![number, xfer],
1968                    vec![xfer, xfer, xfer],
1969                ],
1970            },
1971            // Here, we extend to exactly two wild segments after transfer.
1972            TestCase {
1973                name: "transfer/*/*",
1974                filter: vec![xfer, star, star],
1975                includes: vec![vec![xfer, number, number], vec![xfer, xfer, xfer]],
1976                excludes: vec![
1977                    vec![number],
1978                    vec![number, number],
1979                    vec![number, xfer],
1980                    vec![number, xfer, number, number],
1981                    vec![xfer],
1982                    vec![xfer, xfer, xfer, xfer],
1983                ],
1984            },
1985            // Here, we ensure wildcards can be in the middle of a filter: only
1986            // exact matches happen on the ends, while the middle can be
1987            // anything.
1988            TestCase {
1989                name: "transfer/*/number",
1990                filter: vec![xfer, star, number],
1991                includes: vec![vec![xfer, number, number], vec![xfer, xfer, number]],
1992                excludes: vec![
1993                    vec![number],
1994                    vec![number, number],
1995                    vec![number, number, number],
1996                    vec![number, xfer, number],
1997                    vec![xfer],
1998                    vec![number, xfer],
1999                    vec![xfer, xfer, xfer],
2000                    vec![xfer, number, xfer],
2001                ],
2002            },
2003            // "**" as the sole filter element matches any topic (0+ segments).
2004            TestCase {
2005                name: "**",
2006                filter: vec!["**"],
2007                includes: vec![
2008                    vec![],
2009                    vec![xfer],
2010                    vec![xfer, number],
2011                    vec![xfer, number, number],
2012                ],
2013                excludes: vec![],
2014            },
2015            // "transfer/**" matches "transfer" followed by 0+ segments.
2016            TestCase {
2017                name: "transfer/**",
2018                filter: vec![xfer, "**"],
2019                includes: vec![
2020                    vec![xfer],
2021                    vec![xfer, number],
2022                    vec![xfer, number, number],
2023                    vec![xfer, xfer, xfer],
2024                ],
2025                excludes: vec![
2026                    vec![],
2027                    vec![number],
2028                    vec![number, xfer],
2029                    vec![number, number],
2030                ],
2031            },
2032            // "transfer/number/**" matches exactly "transfer/number" followed
2033            // by 0+ segments.
2034            TestCase {
2035                name: "transfer/number/**",
2036                filter: vec![xfer, number, "**"],
2037                includes: vec![
2038                    vec![xfer, number],
2039                    vec![xfer, number, number],
2040                    vec![xfer, number, xfer, number],
2041                ],
2042                excludes: vec![
2043                    vec![],
2044                    vec![xfer],
2045                    vec![number],
2046                    vec![number, xfer],
2047                    vec![xfer, xfer],
2048                ],
2049            },
2050        ] {
2051            for topic in tc.includes {
2052                assert!(
2053                    does_topic_match(
2054                        &topic
2055                            .iter()
2056                            .map(std::string::ToString::to_string)
2057                            .collect::<Vec<String>>(),
2058                        &tc.filter
2059                            .iter()
2060                            .map(std::string::ToString::to_string)
2061                            .collect::<Vec<String>>()
2062                    ),
2063                    "test: {}, topic ({:?}) should be matched by filter ({:?})",
2064                    tc.name,
2065                    topic,
2066                    tc.filter
2067                );
2068            }
2069
2070            for topic in tc.excludes {
2071                assert!(
2072                    !does_topic_match(
2073                        // make deep copies of the vecs
2074                        &topic
2075                            .iter()
2076                            .map(std::string::ToString::to_string)
2077                            .collect::<Vec<String>>(),
2078                        &tc.filter
2079                            .iter()
2080                            .map(std::string::ToString::to_string)
2081                            .collect::<Vec<String>>()
2082                    ),
2083                    "test: {}, topic ({:?}) should NOT be matched by filter ({:?})",
2084                    tc.name,
2085                    topic,
2086                    tc.filter
2087                );
2088            }
2089        }
2090    }
2091}