apex_sdk/
builder.rs

1//! Apex SDK builder for configuration
2//!
3//! This module provides a builder pattern for configuring and creating
4//! ApexSDK instances with support for Substrate and EVM blockchain adapters.
5//!
6//! # Examples
7//!
8//! ```rust,no_run
9//! use apex_sdk::prelude::*;
10//!
11//! #[tokio::main]
12//! async fn main() -> Result<()> {
13//!     // Configure SDK with both Substrate and EVM support
14//!     let sdk = ApexSDK::builder()
15//!         .with_substrate_endpoint("wss://polkadot.api.onfinality.io/public-ws")
16//!         .with_evm_endpoint("https://mainnet.infura.io/v3/YOUR_KEY")
17//!         .with_timeout(30)
18//!         .build()
19//!         .await?;
20//!
21//!     Ok(())
22//! }
23//! ```
24
25use crate::error::{Error, Result};
26use crate::sdk::ApexSDK;
27
28/// Builder for constructing an ApexSDK instance with customizable configuration.
29///
30/// The builder pattern allows you to configure the SDK with one or both blockchain
31/// adapters (Substrate and EVM) before initialization. At least one adapter must
32/// be configured for the SDK to function.
33///
34/// # Examples
35///
36/// ## Configure with Substrate only
37///
38/// ```rust,no_run
39/// use apex_sdk::prelude::*;
40///
41/// # #[tokio::main]
42/// # async fn main() -> Result<()> {
43/// let sdk = ApexSDK::builder()
44///     .with_substrate_endpoint("wss://polkadot.api.onfinality.io/public-ws")
45///     .build()
46///     .await?;
47/// # Ok(())
48/// # }
49/// ```
50///
51/// ## Configure with EVM only
52///
53/// ```rust,no_run
54/// use apex_sdk::prelude::*;
55///
56/// # #[tokio::main]
57/// # async fn main() -> Result<()> {
58/// let sdk = ApexSDK::builder()
59///     .with_evm_endpoint("https://mainnet.infura.io/v3/YOUR_KEY")
60///     .build()
61///     .await?;
62/// # Ok(())
63/// # }
64/// ```
65///
66/// ## Configure with both adapters
67///
68/// ```rust,no_run
69/// use apex_sdk::prelude::*;
70///
71/// # #[tokio::main]
72/// # async fn main() -> Result<()> {
73/// let sdk = ApexSDK::builder()
74///     .with_substrate_endpoint("wss://polkadot.api.onfinality.io/public-ws")
75///     .with_evm_endpoint("https://mainnet.infura.io/v3/YOUR_KEY")
76///     .with_timeout(60)
77///     .build()
78///     .await?;
79/// # Ok(())
80/// # }
81/// ```
82#[derive(Default)]
83pub struct ApexSDKBuilder {
84    substrate_endpoint: Option<String>,
85    evm_endpoint: Option<String>,
86    timeout_seconds: Option<u64>,
87}
88
89impl ApexSDKBuilder {
90    /// Create a new builder instance.
91    ///
92    /// # Examples
93    ///
94    /// ```rust
95    /// use apex_sdk::builder::ApexSDKBuilder;
96    ///
97    /// let builder = ApexSDKBuilder::new();
98    /// ```
99    pub fn new() -> Self {
100        Self::default()
101    }
102
103    /// Set the Substrate endpoint URL.
104    ///
105    /// This endpoint will be used to connect to Substrate-based blockchains
106    /// like Polkadot and Kusama. The URL should be a WebSocket endpoint
107    /// (typically starting with `wss://` or `ws://`).
108    ///
109    /// # Arguments
110    ///
111    /// * `url` - The WebSocket URL of the Substrate endpoint
112    ///
113    /// # Examples
114    ///
115    /// ```rust
116    /// use apex_sdk::builder::ApexSDKBuilder;
117    ///
118    /// let builder = ApexSDKBuilder::new()
119    ///     .with_substrate_endpoint("wss://polkadot.api.onfinality.io/public-ws");
120    /// ```
121    pub fn with_substrate_endpoint(mut self, url: impl Into<String>) -> Self {
122        self.substrate_endpoint = Some(url.into());
123        self
124    }
125
126    /// Set the EVM endpoint URL.
127    ///
128    /// This endpoint will be used to connect to EVM-compatible blockchains
129    /// like Ethereum, Polygon, BSC, and Avalanche. The URL should be an
130    /// HTTP or HTTPS endpoint.
131    ///
132    /// # Arguments
133    ///
134    /// * `url` - The HTTP(S) URL of the EVM endpoint
135    ///
136    /// # Examples
137    ///
138    /// ```rust
139    /// use apex_sdk::builder::ApexSDKBuilder;
140    ///
141    /// let builder = ApexSDKBuilder::new()
142    ///     .with_evm_endpoint("https://mainnet.infura.io/v3/YOUR_KEY");
143    /// ```
144    pub fn with_evm_endpoint(mut self, url: impl Into<String>) -> Self {
145        self.evm_endpoint = Some(url.into());
146        self
147    }
148
149    /// Set the connection timeout in seconds.
150    ///
151    /// This timeout applies to the initial connection attempts to the
152    /// configured blockchain endpoints. If not set, a default timeout
153    /// will be used.
154    ///
155    /// # Arguments
156    ///
157    /// * `seconds` - Timeout duration in seconds
158    ///
159    /// # Examples
160    ///
161    /// ```rust
162    /// use apex_sdk::builder::ApexSDKBuilder;
163    ///
164    /// let builder = ApexSDKBuilder::new()
165    ///     .with_evm_endpoint("https://mainnet.infura.io/v3/YOUR_KEY")
166    ///     .with_timeout(30);
167    /// ```
168    pub fn with_timeout(mut self, seconds: u64) -> Self {
169        self.timeout_seconds = Some(seconds);
170        self
171    }
172
173    /// Build the ApexSDK instance.
174    ///
175    /// This method consumes the builder and attempts to create an ApexSDK
176    /// instance by connecting to the configured endpoints. At least one
177    /// adapter (Substrate or EVM) must be configured, or this will return
178    /// an error.
179    ///
180    /// # Errors
181    ///
182    /// Returns an error if:
183    /// - No adapters are configured
184    /// - Connection to any configured endpoint fails
185    ///
186    /// # Examples
187    ///
188    /// ```rust,no_run
189    /// use apex_sdk::prelude::*;
190    ///
191    /// # #[tokio::main]
192    /// # async fn main() -> Result<()> {
193    /// let sdk = ApexSDK::builder()
194    ///     .with_evm_endpoint("https://mainnet.infura.io/v3/YOUR_KEY")
195    ///     .build()
196    ///     .await?;
197    ///
198    /// // Use the SDK...
199    /// # Ok(())
200    /// # }
201    /// ```
202    pub async fn build(self) -> Result<ApexSDK> {
203        let substrate_adapter = if let Some(endpoint) = self.substrate_endpoint {
204            Some(
205                apex_sdk_substrate::SubstrateAdapter::connect(&endpoint)
206                    .await
207                    .map_err(Error::Substrate)?,
208            )
209        } else {
210            None
211        };
212
213        let evm_adapter = if let Some(endpoint) = self.evm_endpoint {
214            Some(
215                apex_sdk_evm::EvmAdapter::connect(&endpoint)
216                    .await
217                    .map_err(Error::Evm)?,
218            )
219        } else {
220            None
221        };
222
223        if substrate_adapter.is_none() && evm_adapter.is_none() {
224            return Err(Error::config(
225                "At least one adapter (Substrate or EVM) must be configured",
226            ));
227        }
228
229        Ok(ApexSDK {
230            substrate_adapter,
231            evm_adapter,
232        })
233    }
234}
235
236#[cfg(test)]
237mod tests {
238    use super::*;
239
240    #[tokio::test]
241    async fn test_builder_new_creates_default_builder() {
242        let builder = ApexSDKBuilder::new();
243        assert!(builder.substrate_endpoint.is_none());
244        assert!(builder.evm_endpoint.is_none());
245        assert!(builder.timeout_seconds.is_none());
246    }
247
248    #[tokio::test]
249    async fn test_builder_with_substrate_endpoint() {
250        let builder = ApexSDKBuilder::new().with_substrate_endpoint("wss://test.substrate.io");
251        assert_eq!(
252            builder.substrate_endpoint,
253            Some("wss://test.substrate.io".to_string())
254        );
255    }
256
257    #[tokio::test]
258    async fn test_builder_with_evm_endpoint() {
259        let builder = ApexSDKBuilder::new().with_evm_endpoint("https://test.ethereum.io");
260        assert_eq!(
261            builder.evm_endpoint,
262            Some("https://test.ethereum.io".to_string())
263        );
264    }
265
266    #[tokio::test]
267    async fn test_builder_with_timeout() {
268        let builder = ApexSDKBuilder::new().with_timeout(60);
269        assert_eq!(builder.timeout_seconds, Some(60));
270    }
271
272    #[tokio::test]
273    async fn test_builder_chaining() {
274        let builder = ApexSDKBuilder::new()
275            .with_substrate_endpoint("wss://test.substrate.io")
276            .with_evm_endpoint("https://test.ethereum.io")
277            .with_timeout(120);
278
279        assert_eq!(
280            builder.substrate_endpoint,
281            Some("wss://test.substrate.io".to_string())
282        );
283        assert_eq!(
284            builder.evm_endpoint,
285            Some("https://test.ethereum.io".to_string())
286        );
287        assert_eq!(builder.timeout_seconds, Some(120));
288    }
289
290    #[tokio::test]
291    async fn test_builder_requires_at_least_one_adapter() {
292        let result = ApexSDKBuilder::new().build().await;
293        assert!(result.is_err());
294        match result {
295            Err(Error::Config(msg, _)) => {
296                assert!(msg.contains("At least one adapter"));
297            }
298            _ => panic!("Expected Config error"),
299        }
300    }
301
302    #[tokio::test]
303    async fn test_builder_default_trait() {
304        let builder = ApexSDKBuilder::default();
305        assert!(builder.substrate_endpoint.is_none());
306        assert!(builder.evm_endpoint.is_none());
307    }
308}