nautilus_system/
factories.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2025 Posei Systems Pty Ltd. All rights reserved.
3//  https://poseitrader.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16use std::{any::Any, cell::RefCell, collections::HashMap, fmt::Debug, rc::Rc};
17
18use nautilus_common::{cache::Cache, clock::Clock};
19use posei_trader::client::DataClient;
20use nautilus_execution::client::ExecutionClient;
21
22/// Configuration for creating client instances.
23///
24/// This trait allows different client types to provide their configuration
25/// in a type-safe manner while still being usable in generic factory contexts.
26pub trait ClientConfig: Send + Sync + std::fmt::Debug {
27    /// Return the configuration as a trait object.
28    fn as_any(&self) -> &dyn Any;
29}
30
31/// Factory trait for creating data client instances.
32///
33/// Implementations of this trait should create specific data client types
34/// (e.g., Binance, Bybit, Databento) based on the provided configuration.
35pub trait DataClientFactory: Send + Sync + Debug {
36    /// Create a new data client instance.
37    ///
38    /// # Errors
39    ///
40    /// Returns an error if client creation fails.
41    fn create(
42        &self,
43        name: &str,
44        config: &dyn ClientConfig,
45        cache: Rc<RefCell<Cache>>,
46        clock: Rc<RefCell<dyn Clock>>,
47    ) -> anyhow::Result<Box<dyn DataClient>>;
48
49    /// Returns the name of this factory.
50    fn name(&self) -> &str;
51
52    /// Returns the supported configuration type name for this factory.
53    fn config_type(&self) -> &str;
54}
55
56/// Factory trait for creating execution client instances.
57///
58/// Implementations of this trait should create specific execution client types
59/// (e.g., Binance, Bybit, Interactive Brokers) based on the provided configuration.
60pub trait ExecutionClientFactory: Send + Sync + std::fmt::Debug {
61    /// Create a new execution client instance.
62    ///
63    /// # Errors
64    ///
65    /// Returns an error if client creation fails.
66    fn create(
67        &self,
68        name: &str,
69        config: &dyn ClientConfig,
70        cache: Rc<RefCell<Cache>>,
71        clock: Rc<RefCell<dyn Clock>>,
72    ) -> anyhow::Result<Box<dyn ExecutionClient>>;
73
74    /// Returns the name of this factory.
75    fn name(&self) -> &str;
76
77    /// Returns the supported configuration type name for this factory.
78    fn config_type(&self) -> &str;
79}
80
81/// Registry for managing data client factories.
82///
83/// Allows dynamic registration and lookup of factories by name,
84/// enabling a plugin-like architecture for different data providers.
85#[derive(Debug, Default)]
86pub struct DataClientFactoryRegistry {
87    factories: std::collections::HashMap<String, Box<dyn DataClientFactory>>,
88}
89
90impl DataClientFactoryRegistry {
91    /// Creates a new empty registry.
92    #[must_use]
93    pub fn new() -> Self {
94        Self {
95            factories: std::collections::HashMap::new(),
96        }
97    }
98
99    /// Registers a data client factory.
100    ///
101    /// # Errors
102    ///
103    /// Returns an error if a factory with the same name is already registered.
104    pub fn register(
105        &mut self,
106        name: String,
107        factory: Box<dyn DataClientFactory>,
108    ) -> anyhow::Result<()> {
109        if self.factories.contains_key(&name) {
110            anyhow::bail!("Data client factory '{name}' is already registered");
111        }
112
113        self.factories.insert(name, factory);
114        Ok(())
115    }
116
117    /// Gets a registered factory by name.
118    ///
119    /// # Returns
120    ///
121    /// The factory if found, None otherwise.
122    #[must_use]
123    pub fn get(&self, name: &str) -> Option<&dyn DataClientFactory> {
124        self.factories.get(name).map(std::convert::AsRef::as_ref)
125    }
126
127    /// Gets a list of all registered factory names.
128    #[must_use]
129    pub fn names(&self) -> Vec<&String> {
130        self.factories.keys().collect()
131    }
132
133    /// Checks if a factory is registered.
134    #[must_use]
135    pub fn contains(&self, name: &str) -> bool {
136        self.factories.contains_key(name)
137    }
138}
139
140/// Registry for managing execution client factories.
141///
142/// Allows dynamic registration and lookup of factories by name,
143/// enabling a plugin-like architecture for different execution providers.
144#[derive(Debug, Default)]
145pub struct ExecutionClientFactoryRegistry {
146    factories: HashMap<String, Box<dyn ExecutionClientFactory>>,
147}
148
149impl ExecutionClientFactoryRegistry {
150    /// Creates a new empty registry.
151    #[must_use]
152    pub fn new() -> Self {
153        Self {
154            factories: std::collections::HashMap::new(),
155        }
156    }
157
158    /// Registers an execution client factory.
159    ///
160    /// # Errors
161    ///
162    /// Returns an error if a factory with the same name is already registered.
163    pub fn register(
164        &mut self,
165        name: String,
166        factory: Box<dyn ExecutionClientFactory>,
167    ) -> anyhow::Result<()> {
168        if self.factories.contains_key(&name) {
169            anyhow::bail!("Execution client factory '{name}' is already registered");
170        }
171
172        self.factories.insert(name, factory);
173        Ok(())
174    }
175
176    /// Gets a registered factory by name (if found).
177    #[must_use]
178    pub fn get(&self, name: &str) -> Option<&dyn ExecutionClientFactory> {
179        self.factories.get(name).map(std::convert::AsRef::as_ref)
180    }
181
182    /// Gets a list of all registered factory names.
183    #[must_use]
184    pub fn names(&self) -> Vec<&String> {
185        self.factories.keys().collect()
186    }
187
188    /// Checks if a factory is registered.
189    #[must_use]
190    pub fn contains(&self, name: &str) -> bool {
191        self.factories.contains_key(name)
192    }
193}
194
195////////////////////////////////////////////////////////////////////////////////
196// Tests
197////////////////////////////////////////////////////////////////////////////////
198
199#[allow(dead_code)]
200#[cfg(test)]
201mod tests {
202    use std::any::Any;
203
204    use rstest::*;
205
206    use super::*;
207
208    // Mock configuration for testing
209    #[derive(Debug)]
210    struct MockConfig {
211        #[allow(dead_code)]
212        value: String,
213    }
214
215    impl ClientConfig for MockConfig {
216        fn as_any(&self) -> &dyn Any {
217            self
218        }
219    }
220
221    // Mock data client factory for testing
222    #[derive(Debug)]
223    struct MockDataClientFactory;
224
225    impl DataClientFactory for MockDataClientFactory {
226        fn create(
227            &self,
228            _name: &str,
229            _config: &dyn ClientConfig,
230            _cache: Rc<RefCell<Cache>>,
231            _clock: Rc<RefCell<dyn Clock>>,
232        ) -> anyhow::Result<Box<dyn DataClient>> {
233            // This would create a real client in practice
234            Err(anyhow::anyhow!("Mock factory - not implemented"))
235        }
236
237        fn name(&self) -> &'static str {
238            "mock"
239        }
240
241        fn config_type(&self) -> &'static str {
242            "MockConfig"
243        }
244    }
245
246    #[rstest]
247    fn test_data_client_factory_registry() {
248        let mut registry = DataClientFactoryRegistry::new();
249
250        // Test empty registry
251        assert!(registry.names().is_empty());
252        assert!(!registry.contains("mock"));
253        assert!(registry.get("mock").is_none());
254
255        // Register factory
256        let factory = Box::new(MockDataClientFactory);
257        registry.register("mock".to_string(), factory).unwrap();
258
259        // Test after registration
260        assert_eq!(registry.names().len(), 1);
261        assert!(registry.contains("mock"));
262        assert!(registry.get("mock").is_some());
263
264        // Test duplicate registration fails
265        let factory2 = Box::new(MockDataClientFactory);
266        let result = registry.register("mock".to_string(), factory2);
267        assert!(result.is_err());
268    }
269
270    #[rstest]
271    fn test_empty_data_client_factory_registry() {
272        let registry = DataClientFactoryRegistry::new();
273
274        // Test empty registry
275        assert!(registry.names().is_empty());
276        assert!(!registry.contains("mock"));
277        assert!(registry.get("mock").is_none());
278    }
279
280    #[rstest]
281    fn test_empty_execution_client_factory_registry() {
282        let registry = ExecutionClientFactoryRegistry::new();
283
284        // Test empty registry
285        assert!(registry.names().is_empty());
286        assert!(!registry.contains("mock"));
287        assert!(registry.get("mock").is_none());
288    }
289}