Skip to main content

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    ) -> anyhow::Result<Box<dyn ExecutionClient>>;
75
76    /// Returns the name of this factory.
77    fn name(&self) -> &str;
78
79    /// Returns the supported configuration type name for this factory.
80    fn config_type(&self) -> &str;
81}
82
83/// Registry for managing data client factories.
84///
85/// Allows dynamic registration and lookup of factories by name,
86/// enabling a plugin-like architecture for different data providers.
87#[derive(Debug, Default)]
88pub struct DataClientFactoryRegistry {
89    factories: AHashMap<String, Box<dyn DataClientFactory>>,
90}
91
92impl DataClientFactoryRegistry {
93    /// Creates a new empty registry.
94    #[must_use]
95    pub fn new() -> Self {
96        Self {
97            factories: AHashMap::new(),
98        }
99    }
100
101    /// Registers a data client factory.
102    ///
103    /// # Errors
104    ///
105    /// Returns an error if a factory with the same name is already registered.
106    pub fn register(
107        &mut self,
108        name: String,
109        factory: Box<dyn DataClientFactory>,
110    ) -> anyhow::Result<()> {
111        if self.factories.contains_key(&name) {
112            anyhow::bail!("Data client factory '{name}' is already registered");
113        }
114
115        self.factories.insert(name, factory);
116        Ok(())
117    }
118
119    /// Gets a registered factory by name.
120    ///
121    /// # Returns
122    ///
123    /// The factory if found, None otherwise.
124    #[must_use]
125    pub fn get(&self, name: &str) -> Option<&dyn DataClientFactory> {
126        self.factories.get(name).map(std::convert::AsRef::as_ref)
127    }
128
129    /// Gets a list of all registered factory names.
130    #[must_use]
131    pub fn names(&self) -> Vec<&String> {
132        self.factories.keys().collect()
133    }
134
135    /// Checks if a factory is registered.
136    #[must_use]
137    pub fn contains(&self, name: &str) -> bool {
138        self.factories.contains_key(name)
139    }
140}
141
142/// Registry for managing execution client factories.
143///
144/// Allows dynamic registration and lookup of factories by name,
145/// enabling a plugin-like architecture for different execution providers.
146#[derive(Debug, Default)]
147pub struct ExecutionClientFactoryRegistry {
148    factories: AHashMap<String, Box<dyn ExecutionClientFactory>>,
149}
150
151impl ExecutionClientFactoryRegistry {
152    /// Creates a new empty registry.
153    #[must_use]
154    pub fn new() -> Self {
155        Self {
156            factories: AHashMap::new(),
157        }
158    }
159
160    /// Registers an execution client factory.
161    ///
162    /// # Errors
163    ///
164    /// Returns an error if a factory with the same name is already registered.
165    pub fn register(
166        &mut self,
167        name: String,
168        factory: Box<dyn ExecutionClientFactory>,
169    ) -> anyhow::Result<()> {
170        if self.factories.contains_key(&name) {
171            anyhow::bail!("Execution client factory '{name}' is already registered");
172        }
173
174        self.factories.insert(name, factory);
175        Ok(())
176    }
177
178    /// Gets a registered factory by name (if found).
179    #[must_use]
180    pub fn get(&self, name: &str) -> Option<&dyn ExecutionClientFactory> {
181        self.factories.get(name).map(std::convert::AsRef::as_ref)
182    }
183
184    /// Gets a list of all registered factory names.
185    #[must_use]
186    pub fn names(&self) -> Vec<&String> {
187        self.factories.keys().collect()
188    }
189
190    /// Checks if a factory is registered.
191    #[must_use]
192    pub fn contains(&self, name: &str) -> bool {
193        self.factories.contains_key(name)
194    }
195}
196
197#[allow(dead_code)]
198#[cfg(test)]
199mod tests {
200    use std::any::Any;
201
202    use rstest::*;
203
204    use super::*;
205
206    // Mock configuration for testing
207    #[derive(Debug)]
208    struct MockConfig {
209        #[allow(dead_code)]
210        value: String,
211    }
212
213    impl ClientConfig for MockConfig {
214        fn as_any(&self) -> &dyn Any {
215            self
216        }
217    }
218
219    // Mock data client factory for testing
220    #[derive(Debug)]
221    struct MockDataClientFactory;
222
223    impl DataClientFactory for MockDataClientFactory {
224        fn create(
225            &self,
226            _name: &str,
227            _config: &dyn ClientConfig,
228            _cache: Rc<RefCell<Cache>>,
229            _clock: Rc<RefCell<dyn Clock>>,
230        ) -> anyhow::Result<Box<dyn DataClient>> {
231            // This would create a real client in practice
232            Err(anyhow::anyhow!("Mock factory - not implemented"))
233        }
234
235        fn name(&self) -> &'static str {
236            "mock"
237        }
238
239        fn config_type(&self) -> &'static str {
240            "MockConfig"
241        }
242    }
243
244    #[rstest]
245    fn test_data_client_factory_registry() {
246        let mut registry = DataClientFactoryRegistry::new();
247
248        // Test empty registry
249        assert!(registry.names().is_empty());
250        assert!(!registry.contains("mock"));
251        assert!(registry.get("mock").is_none());
252
253        // Register factory
254        let factory = Box::new(MockDataClientFactory);
255        registry.register("mock".to_string(), factory).unwrap();
256
257        // Test after registration
258        assert_eq!(registry.names().len(), 1);
259        assert!(registry.contains("mock"));
260        assert!(registry.get("mock").is_some());
261
262        // Test duplicate registration fails
263        let factory2 = Box::new(MockDataClientFactory);
264        let result = registry.register("mock".to_string(), factory2);
265        assert!(result.is_err());
266    }
267
268    #[rstest]
269    fn test_empty_data_client_factory_registry() {
270        let registry = DataClientFactoryRegistry::new();
271
272        // Test empty registry
273        assert!(registry.names().is_empty());
274        assert!(!registry.contains("mock"));
275        assert!(registry.get("mock").is_none());
276    }
277
278    #[rstest]
279    fn test_empty_execution_client_factory_registry() {
280        let registry = ExecutionClientFactoryRegistry::new();
281
282        // Test empty registry
283        assert!(registry.names().is_empty());
284        assert!(!registry.contains("mock"));
285        assert!(registry.get("mock").is_none());
286    }
287}