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