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}