Skip to main content

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