Skip to main content

data_anchor_client/
constants.rs

1/// Default number of concurrent requests to send to the RPC.
2pub const DEFAULT_CONCURRENCY: usize = 100;
3
4/// Default number of slots to look back for the
5/// [`crate::client::DataAnchorClient::get_ledger_blobs`] method.
6pub const DEFAULT_LOOKBACK_SLOTS: u64 = 100;
7
8const MAINNET_GENESIS_HASH: &str = "5eykt4UsFv8P8NJdTREpY1vzqKqZKvDPxV6zKj1rS1n";
9const DEVNET_GENESIS_HASH: &str = "EtWTRABZaYq6iMfeYKouRu166VU2xqa1wcaWoxPkrZBG";
10const TESTNET_GENESIS_HASH: &str = "4uhcVJyU9pJkvQyS88uRDiswHXSCkY3zQawwpjk2NsNY";
11
12/// Error types for the indexer URL handling.
13#[derive(thiserror::Error, Debug, PartialEq, Eq)]
14pub enum IndexerUrlError {
15    /// Failed to parse the indexer URL.
16    #[error("Failed to parse indexer URL: {0}")]
17    InvalidUrl(String),
18
19    /// The indexer URL is not supported for the given genesis hash.
20    #[error("Unknown genesis hash: {0}")]
21    UnknownGenesisHash(String),
22
23    /// The indexer URL is not supported for the Testnet.
24    #[error("Testnet is not supported")]
25    TestnetNotSupported,
26}
27
28/// Result type for operations involving the indexer URL.
29pub type IndexerUrlResult<T = ()> = Result<T, IndexerUrlError>;
30
31/// Default Indexer API URL assignments.
32#[derive(Debug, Default, Clone, PartialEq, Eq)]
33pub enum IndexerUrl {
34    Staging,
35    #[default]
36    Devnet,
37    Mainnet,
38    Custom(String),
39}
40
41impl IndexerUrl {
42    /// Returns the indexer URL based on the given Solana genesis hash.
43    pub fn from_genesis_hash(genesis_hash: &str) -> IndexerUrlResult<Self> {
44        match genesis_hash {
45            MAINNET_GENESIS_HASH => Ok(IndexerUrl::Mainnet),
46            DEVNET_GENESIS_HASH => Ok(IndexerUrl::Devnet),
47            TESTNET_GENESIS_HASH => Err(IndexerUrlError::TestnetNotSupported),
48            _ => Err(IndexerUrlError::UnknownGenesisHash(
49                genesis_hash.to_string(),
50            )),
51        }
52    }
53
54    /// Returns the default URL for the given indexer environment.
55    pub fn url(&self) -> String {
56        let name = match self {
57            IndexerUrl::Staging => "staging",
58            IndexerUrl::Devnet => "devnet",
59            IndexerUrl::Mainnet => "mainnet",
60            IndexerUrl::Custom(url) => {
61                return url.clone();
62            }
63        };
64        format!("https://{name}.data-anchor.termina.technology")
65    }
66}
67
68impl std::str::FromStr for IndexerUrl {
69    type Err = IndexerUrlError;
70
71    fn from_str(s: &str) -> Result<Self, Self::Err> {
72        if s.is_empty() {
73            return Err(IndexerUrlError::InvalidUrl(
74                "Indexer URL cannot be empty".to_string(),
75            ));
76        }
77        match s {
78            "staging" => Ok(IndexerUrl::Staging),
79            "devnet" => Ok(IndexerUrl::Devnet),
80            "mainnet" => Ok(IndexerUrl::Mainnet),
81            s if s.starts_with("https://") || s.starts_with("http://") => {
82                Ok(IndexerUrl::Custom(s.to_string()))
83            }
84            _ => Err(IndexerUrlError::InvalidUrl(format!(
85                "URL not a valid protocol, expected `http(s)`: {s}"
86            ))),
87        }
88    }
89}
90
91#[cfg(test)]
92mod tests {
93    use std::str::FromStr;
94
95    use rstest::rstest;
96
97    use super::*;
98
99    #[rstest]
100    #[case::mainnet(MAINNET_GENESIS_HASH, Ok(IndexerUrl::Mainnet))]
101    #[case::devnet(DEVNET_GENESIS_HASH, Ok(IndexerUrl::Devnet))]
102    #[case::testnet(TESTNET_GENESIS_HASH, Err(IndexerUrlError::TestnetNotSupported))]
103    #[case::unknown("unknown_genesis_hash", Err(IndexerUrlError::UnknownGenesisHash("unknown_genesis_hash".to_string())))]
104    fn test_indexer_url_from_genesis_hash(
105        #[case] genesis_hash: &str,
106        #[case] expected: IndexerUrlResult<IndexerUrl>,
107    ) {
108        assert_eq!(IndexerUrl::from_genesis_hash(genesis_hash), expected);
109    }
110
111    #[rstest]
112    #[case::mainnet(IndexerUrl::Mainnet, "https://mainnet.data-anchor.termina.technology")]
113    #[case::devnet(IndexerUrl::Devnet, "https://devnet.data-anchor.termina.technology")]
114    #[case::staging(IndexerUrl::Staging, "https://staging.data-anchor.termina.technology")]
115    #[case::custom(IndexerUrl::Custom("https://custom.indexer.url".to_string()), "https://custom.indexer.url")]
116    fn test_indexer_url_url(#[case] indexer_url: IndexerUrl, #[case] expected_url: &str) {
117        assert_eq!(indexer_url.url(), expected_url);
118    }
119
120    #[rstest]
121    #[case::valid("https://custom.indexer.url", Ok(IndexerUrl::Custom("https://custom.indexer.url".to_string())))]
122    #[case::staging("staging", Ok(IndexerUrl::Staging))]
123    #[case::devnet("devnet", Ok(IndexerUrl::Devnet))]
124    #[case::mainnet("mainnet", Ok(IndexerUrl::Mainnet))]
125    #[case::invalid("invalid_url", Err(IndexerUrlError::InvalidUrl("URL not a valid protocol, expected `http(s)`: invalid_url".to_string())))]
126    fn test_indexer_url_from_str(
127        #[case] input: &str,
128        #[case] expected: IndexerUrlResult<IndexerUrl>,
129    ) {
130        assert_eq!(IndexerUrl::from_str(input), expected);
131    }
132}