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}