sabi/lib.rs
1// Copyright (C) 2024-2026 Takayuki Sato. All Rights Reserved.
2// This program is free software under MIT License.
3// See the file LICENSE in this distribution for more details.
4
5//! This crate provides a small framework for Rust, designed to separate application logic
6//! from data access.
7//!
8//! In this framework, the logic exclusively takes a data access trait as its argument,
9//! and all necessary data access is defined by a single data access trait.
10//! Conversely, the concrete implementations of data access methods are provided as default methods
11//! of `DataAcc` derived traits, allowing for flexible grouping, often by data service.
12//!
13//! The `DataHub` bridges these two parts.
14//! It attaches all `DataAcc` derived traits, and then, using the
15//! [override_macro](https://github.com/sttk/override_macro-rust) crate, it overrides
16//! the methods of the data access trait used by the logic to point to the implementations
17//! found in the `DataAcc` derived traits.
18//! This clever use of this macro compensates for Rust's lack of native method overriding,
19//! allowing the logic to interact with data through an abstract interface.
20//!
21//! Furthermore, the `DataHub` provides transaction control for data operations performed
22//! within the logic.
23//! You can execute logic functions with transaction control using its [`DataHub::txn`] method,
24//! or without transaction control using its [`DataHub::run`] method.
25//!
26//! This framework brings clear separation and robustness to Rust application design.
27
28#![cfg_attr(docsrs, feature(doc_cfg))]
29
30mod async_group;
31mod data_acc;
32mod data_conn;
33mod data_hub;
34mod data_src;
35mod non_null;
36
37use std::collections::HashMap;
38use std::sync::Arc;
39use std::{any, cell, marker, ptr, thread};
40
41pub use async_group::AsyncGroupError;
42pub use data_conn::DataConnError;
43pub use data_hub::DataHubError;
44pub use data_src::{create_static_data_src_container, setup, setup_with_order, uses, DataSrcError};
45
46#[cfg_attr(docsrs, doc(cfg(feature = "tokio")))]
47#[cfg(feature = "tokio")]
48pub mod tokio;
49
50/// The structure that allows for the concurrent execution of multiple functions
51/// using `std::thread` and waits for all of them to complete.
52///
53/// Functions are added using the `add` method and are then run concurrently in separate threads.
54/// The `AsyncGroup` ensures that all tasks finish before proceeding,
55/// and can collect any errors that occur.
56pub struct AsyncGroup {
57 handlers: Vec<(Arc<str>, thread::JoinHandle<errs::Result<()>>)>,
58 pub(crate) _name: Arc<str>,
59}
60
61/// The trait that abstracts a connection per session to an external data service,
62/// such as a database, file system, or messaging service.
63///
64/// Its primary purpose is to enable cohesive transaction operations across multiple
65/// external data services within a single transaction context. Implementations of this
66/// trait provide the concrete input/output operations for their respective data services.
67///
68/// Methods declared within this trait are designed to handle transactional logic.
69/// The [`AsyncGroup`] parameter in various methods allows for concurrent processing
70/// when commit or rollback operations are time-consuming.
71#[allow(unused_variables)] // rustdoc
72pub trait DataConn {
73 /// Attempts to commit the changes made within this data connection's transaction.
74 ///
75 /// This method should encapsulate the logic required to finalize the transaction
76 /// for the specific external data service.
77 ///
78 /// # Parameters
79 ///
80 /// * `ag`: A mutable reference to an [`AsyncGroup`] for potentially offloading
81 /// time-consuming commit operations to a separate thread.
82 ///
83 /// # Returns
84 ///
85 /// * `errs::Result<()>`: `Ok(())` if the commit is successful, or an [`errs::Err`]
86 /// if the commit fails.
87 fn commit(&mut self, ag: &mut AsyncGroup) -> errs::Result<()>;
88
89 /// This method is executed before the transaction commit process for all [`DataConn`] instances
90 /// involved in the transaction.
91 ///
92 /// This method provides a timing to execute unusual commit processes or update operations not
93 /// supported by transactions beforehand.
94 /// This allows other update operations to be rolled back if the operations in this method
95 /// fail.
96 fn pre_commit(&mut self, ag: &mut AsyncGroup) -> errs::Result<()> {
97 Ok(())
98 }
99
100 /// This method is executed after the transaction commit process has successfully completed
101 /// for all [`DataConn`] instances involved in the transaction.
102 ///
103 /// It provides a moment to perform follow-up actions that depend on a successful commit.
104 /// For example, after a database commit, a messaging service's [`DataConn`] might use this
105 /// method to send a "transaction completed" message.
106 ///
107 /// # Parameters
108 ///
109 /// * `ag`: A mutable reference to an [`AsyncGroup`] for potentially offloading
110 /// concurrent post-commit operations.
111 fn post_commit(&mut self, ag: &mut AsyncGroup) {}
112
113 /// Determines whether a "force back" operation is required for this data connection.
114 ///
115 /// A force back is typically executed if one external data service successfully commits
116 /// its changes, but a subsequent external data service within the same transaction fails
117 /// its commit. This method indicates if the committed changes of *this* data service
118 /// need to be undone (forced back).
119 ///
120 /// # Returns
121 ///
122 /// * `bool`: `true` if a force back is needed for this connection, `false` otherwise.
123 fn should_force_back(&self) -> bool {
124 false
125 }
126
127 /// Rolls back any changes made within this data connection's transaction.
128 ///
129 /// This method undoes all operations performed since the beginning of the transaction,
130 /// restoring the data service to its state before the transaction began.
131 ///
132 /// # Parameters
133 ///
134 /// * `ag`: A mutable reference to an [`AsyncGroup`] for potentially offloading
135 /// time-consuming rollback operations to a separate thread.
136 fn rollback(&mut self, ag: &mut AsyncGroup);
137
138 /// Executes an operation to revert committed changes.
139 ///
140 /// This method provides an opportunity to undo changes that were successfully committed
141 /// to this external data service, typically when a commit fails for *another* data service
142 /// within the same distributed transaction, necessitating a rollback of already committed
143 /// changes.
144 ///
145 /// # Parameters
146 ///
147 /// * `ag`: A mutable reference to an [`AsyncGroup`] for potentially offloading
148 /// concurrent force back operations.
149 fn force_back(&mut self, ag: &mut AsyncGroup) {}
150
151 /// Closes the connection to the external data service.
152 ///
153 /// This method should release any resources held by the data connection, ensuring
154 /// a graceful shutdown of the connection.
155 fn close(&mut self);
156}
157
158struct NoopDataConn {}
159
160impl DataConn for NoopDataConn {
161 fn commit(&mut self, _ag: &mut AsyncGroup) -> errs::Result<()> {
162 Ok(())
163 }
164 fn rollback(&mut self, _ag: &mut AsyncGroup) {}
165 fn close(&mut self) {}
166}
167
168#[repr(C)]
169struct DataConnContainer<C = NoopDataConn>
170where
171 C: DataConn + 'static,
172{
173 drop_fn: fn(*const DataConnContainer),
174 is_fn: fn(any::TypeId) -> bool,
175 commit_fn: fn(*const DataConnContainer, &mut AsyncGroup) -> errs::Result<()>,
176 pre_commit_fn: fn(*const DataConnContainer, &mut AsyncGroup) -> errs::Result<()>,
177 post_commit_fn: fn(*const DataConnContainer, &mut AsyncGroup),
178 should_force_back_fn: fn(*const DataConnContainer) -> bool,
179 rollback_fn: fn(*const DataConnContainer, &mut AsyncGroup),
180 force_back_fn: fn(*const DataConnContainer, &mut AsyncGroup),
181 close_fn: fn(*const DataConnContainer),
182
183 name: Arc<str>,
184 data_conn: Box<C>,
185}
186
187struct DataConnManager {
188 vec: Vec<Option<ptr::NonNull<DataConnContainer>>>,
189 index_map: HashMap<Arc<str>, usize>,
190}
191
192/// The trait that abstracts a data source responsible for managing connections
193/// to external data services, such as databases, file systems, or messaging services.
194///
195/// It receives configuration for connecting to an external data service and then
196/// creates and supplies [`DataConn`] instance, representing a single session connection.
197#[allow(unused_variables)] // for rustdoc
198pub trait DataSrc<C>
199where
200 C: DataConn + 'static,
201{
202 /// Performs the setup process for the data source.
203 ///
204 /// This method is responsible for establishing global connections, configuring
205 /// connection pools, or performing any necessary initializations required
206 /// before [`DataConn`] instances can be created.
207 ///
208 /// # Parameters
209 ///
210 /// * `ag`: A mutable reference to an [`AsyncGroup`]. This is used if the setup
211 /// process is potentially time-consuming and can benefit from concurrent
212 /// execution in a separate thread.
213 ///
214 /// # Returns
215 ///
216 /// * `errs::Result<()>`: `Ok(())` if the setup is successful, or an [`errs::Err`]
217 /// if any part of the setup fails.
218 fn setup(&mut self, ag: &mut AsyncGroup) -> errs::Result<()>;
219
220 /// Closes the data source and releases any globally held resources.
221 ///
222 /// This method should perform cleanup operations, such as closing global connections
223 /// or shutting down connection pools, that were established during the setup process.
224 fn close(&mut self);
225
226 /// Creates a new [`DataConn`] instance which is a connection per session.
227 ///
228 /// Each call to this method should yield a distinct [`DataConn`] object tailored
229 /// for a single session's operations.
230 ///
231 /// # Returns
232 ///
233 /// * `errs::Result<Box<C>>`: `Ok(Box<C>)` containing the newly created [`DataConn`]
234 /// if successful, or an [`errs::Err`] if the connection could not be created.
235 fn create_data_conn(&mut self) -> errs::Result<Box<C>>;
236}
237
238struct NoopDataSrc {}
239
240impl DataSrc<NoopDataConn> for NoopDataSrc {
241 fn setup(&mut self, _ag: &mut AsyncGroup) -> errs::Result<()> {
242 Ok(())
243 }
244 fn close(&mut self) {}
245 fn create_data_conn(&mut self) -> errs::Result<Box<NoopDataConn>> {
246 Ok(Box::new(NoopDataConn {}))
247 }
248}
249
250#[repr(C)]
251struct DataSrcContainer<S = NoopDataSrc, C = NoopDataConn>
252where
253 S: DataSrc<C>,
254 C: DataConn + 'static,
255{
256 drop_fn: fn(*const DataSrcContainer),
257 setup_fn: fn(*const DataSrcContainer, &mut AsyncGroup) -> errs::Result<()>,
258 close_fn: fn(*const DataSrcContainer),
259 create_data_conn_fn: fn(*const DataSrcContainer) -> errs::Result<Box<DataConnContainer<C>>>,
260 is_data_conn_fn: fn(any::TypeId) -> bool,
261
262 local: bool,
263 name: Arc<str>,
264 data_src: S,
265}
266
267struct DataSrcManager {
268 vec_unready: Vec<SendSyncNonNull<DataSrcContainer>>,
269 vec_ready: Vec<SendSyncNonNull<DataSrcContainer>>,
270 local: bool,
271}
272
273/// A utility struct that ensures to close and drop global data sources when it goes out of scope.
274///
275/// This struct implements the `Drop` trait, and its `drop` method handles the closing and
276/// dropping of registered global data sources.
277/// Therefore, this ensures that these operations are automatically executed at the end of
278/// the scope.
279///
280/// **NOTE:** Do not receive an instance of this struct into an anonymous variable
281/// (`let _ = ...`), because an anonymous variable is dropped immediately at that point.
282pub struct AutoShutdown {}
283
284/// The struct that acts as a central hub for data input/output operations, integrating
285/// multiple *Data* traits (which are passed to business logic functions as their arguments) with
286/// [`DataAcc`] traits (which implement default data I/O methods for external services).
287///
288/// It facilitates data access by providing [`DataConn`] objects, created from
289/// both global data sources (registered via the global [`uses!`] macro) and
290/// session-local data sources (registered via [`DataHub::uses`] method).
291///
292/// The [`DataHub`] is capable of performing aggregated transactional operations
293/// on all [`DataConn`] objects created from its registered [`DataSrc`] instances.
294pub struct DataHub {
295 local_data_src_manager: DataSrcManager,
296 data_src_map: HashMap<Arc<str>, (bool, usize)>,
297 data_conn_manager: DataConnManager,
298 fixed: bool,
299}
300
301/// This trait provides a mechanism to retrieve a mutable reference to a [`DataConn`] object
302/// by name, creating it if necessary.
303///
304/// It is typically implemented as a derived trait with default methods (using
305/// the `override_macro` crate) on [`DataHub`], allowing application logic to
306/// interact with data services through an abstract interface.
307pub trait DataAcc {
308 /// Retrieves a mutable reference to a [`DataConn`] object by name, creating it if necessary.
309 ///
310 /// This is the core method used by [`DataAcc`] implementations to obtain connections
311 /// to external data services. It first checks if a [`DataConn`] with the given name
312 /// already exists in the current session. If not, it attempts to find a
313 /// corresponding [`DataSrc`] and create a new [`DataConn`] from it.
314 ///
315 /// # Type Parameters
316 ///
317 /// * `C`: The concrete type of [`DataConn`] expected.
318 ///
319 /// # Parameters
320 ///
321 /// * `name`: The name of the data source/connection to retrieve.
322 ///
323 /// # Returns
324 ///
325 /// * `errs::Result<&mut C>`: A mutable reference to the [`DataConn`] instance if successful,
326 /// or an [`errs::Err`] if the data source is not found, or if the retrieved/created
327 /// [`DataConn`] cannot be cast to the specified type `C`.
328 fn get_data_conn<C: DataConn + 'static>(
329 &mut self,
330 name: impl AsRef<str>,
331 ) -> errs::Result<&mut C>;
332}
333
334#[doc(hidden)]
335pub struct StaticDataSrcContainer {
336 ssnnptr: SendSyncNonNull<DataSrcContainer>,
337}
338
339#[doc(hidden)]
340pub struct StaticDataSrcRegistration {
341 factory: fn() -> StaticDataSrcContainer,
342}
343
344struct SendSyncNonNull<T: Send + Sync> {
345 non_null_ptr: ptr::NonNull<T>,
346 _phantom: marker::PhantomData<cell::Cell<T>>,
347}