Skip to main content

binary_options_tools_core_pre/
traits.rs

1use async_trait::async_trait;
2use kanal::{AsyncReceiver, AsyncSender};
3use std::fmt::Debug;
4use std::sync::Arc;
5use tokio_tungstenite::tungstenite::Message;
6
7use crate::error::CoreResult;
8
9#[derive(Debug, Clone, Copy)]
10pub enum RunnerCommand {
11    Disconnect,
12    Shutdown, // This can be used to gracefully shut down the runner
13    Connect,
14    Reconnect,
15}
16
17/// The contract for the application's shared state.
18#[async_trait]
19pub trait AppState: Send + Sync + 'static {
20    /// Clears any temporary data from the state, called on a manual disconnect.
21    async fn clear_temporal_data(&self);
22}
23
24#[async_trait]
25impl AppState for () {
26    async fn clear_temporal_data(&self) {
27        // Default implementation does nothing.
28    }
29}
30
31/// The contract for a self-contained, concurrent API module.
32/// Generic over the `AppState` for type-safe access to shared data.
33#[async_trait]
34pub trait ApiModule<S: AppState>: Send + 'static {
35    /// The specific command type this module accepts.
36    type Command: Debug + Send;
37    /// This specific CommandResponse type this module produces.
38    type CommandResponse: Debug + Send;
39    /// The handle that users will interact with. It must be clonable.
40    type Handle: Clone + Send + Sync + 'static;
41
42    /// Creates a new instance of the module.
43    #[allow(clippy::too_many_arguments)]
44    fn new(
45        shared_state: Arc<S>,
46        command_receiver: AsyncReceiver<Self::Command>,
47        command_responder: AsyncSender<Self::CommandResponse>,
48        message_receiver: AsyncReceiver<Arc<Message>>,
49        to_ws_sender: AsyncSender<Message>,
50        runner_command_tx: AsyncSender<RunnerCommand>,
51    ) -> Self
52    where
53        Self: Sized;
54
55    /// Creates a new handle for this module.
56    /// This is used to send commands to the module.
57    ///
58    /// # Arguments
59    /// * `sender`: The sender channel for commands.
60    /// * `receiver`: The receiver channel for command responses.
61    fn create_handle(
62        sender: AsyncSender<Self::Command>,
63        receiver: AsyncReceiver<Self::CommandResponse>,
64    ) -> Self::Handle;
65
66    #[allow(clippy::too_many_arguments)]
67    fn new_combined(
68        shared_state: Arc<S>,
69        command_receiver: AsyncReceiver<Self::Command>,
70        command_responder: AsyncSender<Self::Command>,
71        command_response_receiver: AsyncReceiver<Self::CommandResponse>,
72        command_response_responder: AsyncSender<Self::CommandResponse>,
73        message_receiver: AsyncReceiver<Arc<Message>>,
74        to_ws_sender: AsyncSender<Message>,
75        runner_command_tx: AsyncSender<RunnerCommand>,
76    ) -> (Self, Self::Handle)
77    where
78        Self: Sized,
79    {
80        let module = Self::new(
81            shared_state,
82            command_receiver,
83            command_response_responder,
84            message_receiver,
85            to_ws_sender,
86            runner_command_tx,
87        );
88        let handle = Self::create_handle(command_responder, command_response_receiver);
89        (module, handle)
90    }
91
92    /// The main run loop for the module's background task.
93    async fn run(&mut self) -> CoreResult<()>;
94
95    /// An optional callback that can be executed when a reconnection event occurs.
96    /// This function is useful for modules that need to perform specific actions
97    /// when a reconnection happens, such as reinitializing state or resending messages.
98    /// It allows for custom behavior to be defined that can be executed in the context of the
99    /// module, providing flexibility and extensibility to the module's functionality.
100    fn callback(
101        _shared_state: Arc<S>,
102        _command_receiver: AsyncReceiver<Self::Command>,
103        _command_responder: AsyncSender<Self::CommandResponse>,
104        _message_receiver: AsyncReceiver<Arc<Message>>,
105        _to_ws_sender: AsyncSender<Message>,
106    ) -> CoreResult<Option<Box<dyn ReconnectCallback<S>>>> {
107        // Default implementation does nothing.
108        // This is useful for modules that do not require a callback.
109        Ok(None)
110    }
111
112    /// Route only messages for which this returns true.
113    /// This function is used to determine whether a message should be processed by this module.
114    /// It allows for flexible and reusable rules that can be applied to different modules.
115    /// The main difference between this and the `LightweightModule` rule is that
116    /// this rule also takes the shared state as an argument, allowing for more complex
117    /// routing logic that can depend on the current state of the application.
118    fn rule(state: Arc<S>) -> Box<dyn Rule + Send + Sync>;
119}
120
121/// A self‐contained module that runs independently,
122/// owns its recv/sender channels and shared state,
123/// and processes incoming WS messages according to its routing rule.
124/// It's main difference from `ApiModule` is that it does not
125/// require a command-response mechanism and is not intended to be used
126/// as a part of the API, but rather as a lightweight module that can
127/// process messages in a more flexible way.
128/// It is useful for modules that need to handle messages without the overhead of a full API module
129/// and can be used for tasks like logging, monitoring, or simple message transformations.
130/// It is designed to be lightweight and efficient, allowing for quick processing of messages
131/// without the need for a full command-response cycle.
132/// It is also useful for modules that need to handle messages in a more flexible way,
133/// such as forwarding messages to other parts of the system or performing simple transformations.
134/// It is not intended to be used as a part of the API, but rather as a
135/// lightweight module that can process messages in a more flexible way.
136///
137/// The main difference from the `LightweightHandler` type is that this trait is intended for
138/// modules that need to manage their own state and processing logic and being run in a dedicated task.,
139/// allowing easy automation of things like sending periodic messages to a websocket connection to keep it alive.
140#[async_trait]
141pub trait LightweightModule<S: AppState>: Send + 'static {
142    /// Construct the module with:
143    /// - shared app state
144    /// - a sender for outgoing WS messages
145    /// - a receiver for incoming WS messages
146    fn new(
147        state: Arc<S>,
148        ws_sender: AsyncSender<Message>,
149        ws_receiver: AsyncReceiver<Arc<Message>>,
150        runner_command_tx: AsyncSender<RunnerCommand>,
151    ) -> Self
152    where
153        Self: Sized;
154
155    /// The module's asynchronous run loop.
156    async fn run(&mut self) -> CoreResult<()>;
157
158    /// Route only messages for which this returns true.
159    fn rule() -> Box<dyn Rule + Send + Sync>;
160}
161
162/// Data returned by the rule function of a module.
163/// This trait is used to define the rules that determine whether a message should be processed by a module.
164/// It allows for flexible and reusable rules that can be applied to different modules.
165/// The rules can be implemented as standalone functions or as methods on the module itself.
166/// The rules should be lightweight and efficient, as they will be called for every incoming message.
167/// The rules should not perform any blocking operations and should be designed to be as efficient as possible
168/// to avoid slowing down the message processing pipeline.
169/// The rules can be used to filter messages, transform them, or perform any other necessary operations
170pub trait Rule {
171    /// Validate wherever the messsage follows the rule and needs to be processed by this module.
172    fn call(&self, msg: &Message) -> bool;
173
174    /// Resets the rule to its initial state.
175    /// This is useful for rules that maintain state and need to be reset
176    /// when the module is reset or reinitialized.
177    /// Implementations should ensure that the rule is in a clean state after this call.
178    /// # Note
179    /// This method is not required to be asynchronous, as it is expected to be a lightweight
180    /// operation that does not involve any I/O or long-running tasks.
181    /// It should be implemented in a way that allows the rule to be reused without
182    /// needing to recreate it, thus improving performance and reducing overhead.
183    fn reset(&self);
184}
185
186/// A trait for callback functions that can be executed within the context of a module.
187/// This trait is designed to allow modules to define custom behavior that can be executed
188/// when a reconnection event occurs.
189#[async_trait]
190pub trait ReconnectCallback<S: AppState>: Send + Sync {
191    /// The asynchronous function that will be called when a reconnection event occurs.
192    /// This function receives the shared state and a sender for outgoing WebSocket messages.
193    /// It should return a `CoreResult<()>` indicating the success or failure of the operation.
194    /// /// # Arguments
195    /// * `state`: The shared application state that the callback can use.
196    /// * `ws_sender`: The sender for outgoing WebSocket messages, allowing the callback to
197    ///   send messages to the WebSocket connection.
198    /// # Returns
199    /// A `CoreResult<()>` indicating the success or failure of the operation.
200    /// # Note
201    /// This function is expected to be asynchronous, allowing it to perform I/O operations
202    /// or other tasks that may take time without blocking the event loop.
203    /// Implementations should ensure that they handle any potential errors gracefully
204    /// and return appropriate results.
205    /// It is also important to ensure that the callback does not block the event loop,
206    /// as this could lead to performance issues in the application.
207    /// Implementations should be designed to be efficient and non-blocking,
208    /// allowing the application to continue processing other events while the callback is executed.
209    /// This trait is useful for defining custom behavior that can be executed when a reconnection event
210    /// occurs, allowing modules to handle reconnections in a flexible and reusable way.    
211    async fn call(&self, state: Arc<S>, ws_sender: &AsyncSender<Message>) -> CoreResult<()>;
212}
213
214impl<F> Rule for F
215where
216    F: Fn(&Message) -> bool + Send + Sync + 'static,
217{
218    fn call(&self, msg: &Message) -> bool {
219        self(msg)
220    }
221
222    fn reset(&self) {
223        // Default implementation does nothing.
224        // This is useful for stateless rules.
225    }
226}
227
228#[async_trait]
229impl<S: AppState> ReconnectCallback<S> for () {
230    async fn call(&self, _state: Arc<S>, _ws_sender: &AsyncSender<Message>) -> CoreResult<()> {
231        Ok(())
232    }
233}