nautilus_system/
factories.rs

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