nautilus_system/
factories.rs

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