sdecay/
database.rs

1//! Defines safe outer database types
2//!
3//! Unsafe: no
4
5use core::{fmt::Debug, ops::Deref, pin::Pin};
6
7use crate::{
8    as_cpp_string::AsCppString,
9    container::{Container, RefContainer},
10    wrapper::{CppException, SandiaDecayDataBase},
11};
12
13/// `SandiaDecay`'s database with no info actually stored. Technically, it's already initialized, but I assume none of the calls would return meaningful info (so none are exposed)
14///
15/// To be used in any meaningful way, you need to obtain [`GenericDatabase`] using one of the following methods:
16/// - [`GenericUninitDatabase::init`]
17/// - [`GenericUninitDatabase::init_bytes`]
18/// - [`GenericUninitDatabase::init_env`]
19///
20/// See respective docs for details
21pub struct GenericUninitDatabase<C: Container<Inner = SandiaDecayDataBase>>(C);
22
23/// Not initialized database stored in the [`alloc::boxed::Box`]
24///
25/// For more details, see [`GenericUninitDatabase`]
26#[cfg(feature = "alloc")]
27pub type UninitDatabase =
28    GenericUninitDatabase<crate::container::BoxContainer<SandiaDecayDataBase>>;
29/// Not initialized database stored in the [`std::sync::Arc`]
30///
31/// For more details, see [`GenericUninitDatabase`]
32#[cfg(feature = "alloc")]
33pub type UninitSharedDatabase =
34    GenericUninitDatabase<crate::container::ArcContainer<SandiaDecayDataBase>>;
35/// Not initialized database stored in wherever the `&`[`core::mem::MaybeUninit`] pointed to
36///
37/// For more details, see [`GenericUninitDatabase`]
38pub type UninitLocalDatabase<'l> = GenericUninitDatabase<RefContainer<'l, SandiaDecayDataBase>>;
39
40impl<C: Container<Inner = SandiaDecayDataBase>> Debug for GenericUninitDatabase<C> {
41    #[inline]
42    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
43        f.write_str("UninintDatabase")
44    }
45}
46
47impl<C: Container<Inner = SandiaDecayDataBase>> Default for GenericUninitDatabase<C>
48where
49    C::Allocator: Default,
50{
51    #[inline]
52    fn default() -> Self {
53        Self::new()
54    }
55}
56
57impl<C: Container<Inner = SandiaDecayDataBase>> GenericUninitDatabase<C> {
58    /// Allocates empty database
59    #[inline]
60    pub fn new_in(allocator: C::Allocator) -> Self {
61        Self(SandiaDecayDataBase::new(allocator))
62    }
63
64    /// Same as [`GenericUninitDatabase::new_in`], but allocator is created via [`Default::default`]
65    #[inline]
66    pub fn new() -> Self
67    where
68        C::Allocator: Default,
69    {
70        Self(SandiaDecayDataBase::new(C::Allocator::default()))
71    }
72
73    fn get_mut(&mut self) -> Pin<&mut SandiaDecayDataBase> {
74        self.0
75            .try_inner()
76            .expect("Uninit database should not be in a shared container")
77    }
78
79    /// Attempts to initialize the database via path to the database `.xml` file
80    ///
81    /// ### Returns
82    /// - [`Result::Ok`] indicates successfully initialized database
83    /// - [`Result::Err`] indicates a failure to initialize a database. Actually returned value is a tuple of uninitialized database and exception thrown on C++ side
84    ///
85    /// ### Example
86    /// An example using [`crate::container::BoxContainer`] for storage:
87    /// ```rust,no_run
88    /// # #[cfg(feature = "alloc")] {
89    /// # use sdecay::database::UninitDatabase;
90    /// // assuming `database.xml` contains database data
91    /// let database = UninitDatabase::new()
92    ///     .init("database.xml")
93    ///     .expect("`database.xml` should exist and contain a valid database data");
94    /// # }
95    /// ```
96    ///
97    /// Note, that path can be any [`AsCppString`] implementor - see it's doc to find the most convenient for you
98    pub fn init(
99        mut self,
100        path: impl AsCppString,
101    ) -> Result<GenericDatabase<C>, (GenericUninitDatabase<C>, CppException)> {
102        match self.get_mut().init_path(path) {
103            Ok(()) => Ok(GenericDatabase(self.0)),
104            Err(exception) => Err((self, exception)),
105        }
106    }
107
108    /// Attempts to initialize the database via `xml` data
109    ///
110    /// ### Returns
111    /// - [`Result::Ok`] indicates successfully initialized database
112    /// - [`Result::Err`] indicates a failure to initialize a database. Actually returned value is a tuple of uninitialized database and exception thrown on C++ side
113    ///
114    /// ### Example
115    /// An example using [`crate::container::BoxContainer`] for storage:
116    /// ```rust,no_run
117    /// # #[cfg(feature = "alloc")] {
118    /// let data: &[u8] = br#"<?xml version="1.0"?><document>...</document>"#; // assuming `data contains valid database data`
119    /// # use sdecay::database::UninitDatabase;
120    /// let database = UninitDatabase::new()
121    ///     .init_bytes(data)
122    ///     .expect("Should provide valid database data");
123    /// # }
124    /// ```
125    pub fn init_bytes(
126        mut self,
127        bytes: impl AsRef<[u8]>,
128    ) -> Result<GenericDatabase<C>, (GenericUninitDatabase<C>, CppException)> {
129        match self.get_mut().init_bytes(bytes) {
130            Ok(()) => Ok(GenericDatabase(self.0)),
131            Err(exception) => Err((self, exception)),
132        }
133    }
134}
135
136/// Error while initializing database by path from environment variable
137///
138/// Returned by [`GenericUninitDatabase::init_env`] and [`GenericDatabase::from_env`]
139#[derive(Debug, Error)]
140pub enum EnvInitError {
141    /// `SANDIA_DATABASE_PATH` is not present in the environment
142    #[error("No `SANDIA_DATABASE_PATH` variable in the environment")]
143    NoEnvVar,
144    /// Envvar present, but exception thrown from C++ side
145    #[error(transparent)]
146    Exception(CppException),
147}
148
149impl<C: Container<Inner = SandiaDecayDataBase>> GenericUninitDatabase<C> {
150    /// Attempts to initialize database by a path from `SANDIA_DATABASE_PATH` environment variable
151    /// ### Returns
152    /// - [`Result::Ok`] indicates successfully initialized database
153    /// - [`Result::Err`] indicates a failure to initialize a database. Actually returned value is a tuple of uninitialized database and [`EnvInitError`]
154    ///
155    /// ### Example
156    /// An example using [`crate::container::BoxContainer`] for storage:
157    /// ```rust,no_run
158    /// // assuming `SANDIA_DATABASE_PATH` envvar contains path to database file
159    /// # use sdecay::database::UninitDatabase;
160    /// let database = UninitDatabase::new()
161    ///     .init_env()
162    ///     .expect("`SANDIA_DATABASE_PATH` should contain path to data, and data should be valid");
163    /// ```
164    #[cfg(feature = "std")]
165    #[inline]
166    pub fn init_env(self) -> Result<GenericDatabase<C>, (GenericUninitDatabase<C>, EnvInitError)> {
167        let Some(env_path) = std::env::var_os("SANDIA_DATABASE_PATH") else {
168            return Err((self, EnvInitError::NoEnvVar));
169        };
170        self.init(env_path)
171            .map_err(|(uninit, exception)| (uninit, EnvInitError::Exception(exception)))
172    }
173}
174
175impl<C: Container<Inner = SandiaDecayDataBase>> GenericUninitDatabase<C> {
176    /// Creates initialized database from embedded "default" database
177    ///
178    /// ### Example
179    /// Using [`crate::container::BoxContainer`] for storage:
180    /// ```rust
181    /// # #[cfg(feature = "alloc")] {
182    /// # use sdecay::database::UninitDatabase;
183    /// let database = UninitDatabase::new().init_vendor();
184    /// # }
185    /// ```
186    #[cfg(feature = "database")]
187    #[inline]
188    pub fn init_vendor(self) -> GenericDatabase<C> {
189        self.init_bytes(sdecay_sys::database::DATABASE)
190            .expect("Embedded database should be valid")
191    }
192
193    /// Creates initialized database from embedded "min" database
194    ///
195    /// ### Example
196    /// Using [`crate::container::BoxContainer`] for storage:
197    /// ```rust
198    /// # #[cfg(feature = "alloc")] {
199    /// # use sdecay::database::UninitDatabase;
200    /// let database = UninitDatabase::new().init_vendor_min();
201    /// # }
202    /// ```
203    #[cfg(feature = "database-min")]
204    #[inline]
205    pub fn init_vendor_min(self) -> GenericDatabase<C> {
206        self.init_bytes(sdecay_sys::database::DATABASE_MIN)
207            .expect("Embedded database should be valid")
208    }
209
210    /// Creates initialized database from embedded "nocoinc-min" database
211    ///
212    /// ### Example
213    /// Using [`crate::container::BoxContainer`] for storage:
214    /// ```rust
215    /// # #[cfg(feature = "alloc")] {
216    /// # use sdecay::database::UninitDatabase;
217    /// let database = UninitDatabase::new().init_vendor_nocoinc_min();
218    /// # }
219    /// ```
220    #[cfg(feature = "database-nocoinc-min")]
221    #[inline]
222    pub fn init_vendor_nocoinc_min(self) -> GenericDatabase<C> {
223        self.init_bytes(sdecay_sys::database::DATABASE_NOCOINC_MIN)
224            .expect("Embedded database should be valid")
225    }
226}
227
228/// Initialized and data-enabled `SandiaDecay` database. Can be created from [`GenericUninitDatabase`] (see it's doc), or directly via
229/// - [`GenericDatabase::from_path`] ([`GenericDatabase::from_path_in`])
230/// - [`GenericDatabase::from_bytes`] ([`GenericDatabase::from_bytes_in`])
231/// - [`GenericDatabase::from_env`] ([`GenericDatabase::from_env_in`])
232///
233/// See functions below for usage examples
234#[derive(Debug, Clone)]
235pub struct GenericDatabase<C: Container<Inner = SandiaDecayDataBase>>(C);
236
237/// Initialized database stored in the [`alloc::boxed::Box`]
238///
239/// For more details, see [`GenericDatabase`]
240#[cfg(feature = "alloc")]
241pub type Database = GenericDatabase<crate::container::BoxContainer<SandiaDecayDataBase>>;
242/// Initialized database stored in the [`std::sync::Arc`]
243///
244/// For more details, see [`GenericDatabase`]
245#[cfg(feature = "alloc")]
246pub type SharedDatabase = GenericDatabase<crate::container::ArcContainer<SandiaDecayDataBase>>;
247/// Initialized database stored in wherever the `&`[`core::mem::MaybeUninit`] pointed to
248///
249/// For more details, see [`GenericDatabase`]
250pub type LocalDatabase<'l> = GenericDatabase<RefContainer<'l, SandiaDecayDataBase>>;
251
252impl<C: Container<Inner = SandiaDecayDataBase>> GenericDatabase<C> {
253    /// Attempts to create initialized database via path to the database `.xml` file
254    ///
255    /// This is the same as consequent [`UninitDatabase::new`] and [`UninitDatabase::init`] calls
256    ///
257    /// ### Returns
258    /// - [`Result::Ok`] successfully initialized database
259    /// - [`Result::Err`] contains a description of panic from C++ side
260    ///
261    /// ### Example
262    /// An example using [`crate::container::BoxContainer`] for storage:
263    /// ```rust,no_run
264    /// # #[cfg(feature = "alloc")] {
265    /// # use sdecay::database::Database;
266    /// // assuming `database.xml` contains database data
267    /// let database = Database::from_path("database.xml")
268    ///     .expect("`database.xml` should exist and contain a valid database data");
269    /// # }
270    /// ```
271    ///
272    /// Note, that path can be any [`AsCppString`] implementor - see it's doc to find the most convenient for you
273    #[inline]
274    pub fn from_path_in(
275        allocator: C::Allocator,
276        path: impl AsCppString,
277    ) -> Result<Self, CppException> {
278        match GenericUninitDatabase::new_in(allocator).init(path) {
279            Ok(init) => Ok(init),
280            Err((_, error)) => Err(error),
281        }
282    }
283
284    /// Same as [`Self::from_path_in`], but uses `C::Allocator`'s [`Default`] implementation to obtain the allocator
285    #[inline]
286    pub fn from_path(path: impl AsCppString) -> Result<Self, CppException>
287    where
288        C::Allocator: Default,
289    {
290        Self::from_path_in(C::Allocator::default(), path)
291    }
292
293    /// Attempts to create initialized database via `xml` data
294    ///
295    /// This is the same as consequent [`UninitDatabase::new`] and [`UninitDatabase::init_bytes`] calls
296    ///
297    /// ### Returns
298    /// - [`Result::Ok`] successfully initialized database
299    /// - [`Result::Err`] contains a description of panic from C++ side
300    ///
301    /// ### Example
302    /// An example using [`crate::container::BoxContainer`] for storage:
303    /// ```rust,no_run
304    /// # #[cfg(feature = "alloc")] {
305    /// # use sdecay::database::Database;
306    /// let data: &[u8] = br#"<?xml version="1.0"?><document>...</document>"#; // assuming `data contains valid database data`
307    /// let database = Database::from_bytes(data)
308    ///     .expect("Should provide valid database data");
309    /// # }
310    /// ```
311    #[inline]
312    pub fn from_bytes_in(
313        allocator: C::Allocator,
314        bytes: impl AsRef<[u8]>,
315    ) -> Result<Self, CppException> {
316        match GenericUninitDatabase::new_in(allocator).init_bytes(bytes) {
317            Ok(init) => Ok(init),
318            Err((_, error)) => Err(error),
319        }
320    }
321
322    /// Same as [`Self::from_bytes_in`], but uses `C::Allocator`'s [`Default`] implementation to obtain the allocator
323    #[inline]
324    pub fn from_bytes(bytes: impl AsRef<[u8]>) -> Result<Self, CppException>
325    where
326        C::Allocator: Default,
327    {
328        Self::from_bytes_in(C::Allocator::default(), bytes)
329    }
330
331    /// Attempts to create initialized database by a path from `SANDIA_DATABASE_PATH` environment variable
332    ///
333    /// This is the same as consequent [`UninitDatabase::new`] and [`UninitDatabase::init_bytes`] calls
334    ///
335    /// ### Returns
336    /// - [`Result::Ok`] successfully initialized database
337    /// - [`Result::Err`] contains a description of panic from C++ side
338    ///
339    /// ### Example
340    /// An example using [`crate::container::BoxContainer`] for storage:
341    /// ```rust,no_run
342    /// # use sdecay::database::Database;
343    /// // assuming `SANDIA_DATABASE_PATH` envvar contains path to database file
344    /// let database = Database::from_env()
345    ///     .expect("`SANDIA_DATABASE_PATH` should contain path to data, and data should be valid");
346    /// ```
347    #[cfg(feature = "std")]
348    #[inline]
349    pub fn from_env_in(allocator: C::Allocator) -> Result<Self, EnvInitError> {
350        match GenericUninitDatabase::new_in(allocator).init_env() {
351            Ok(init) => Ok(init),
352            Err((_, error)) => Err(error),
353        }
354    }
355
356    /// Same as [`Self::from_env_in`], but uses `C::Allocator`'s [`Default`] implementation to obtain the allocator
357    #[cfg(feature = "std")]
358    #[inline]
359    pub fn from_env() -> Result<Self, EnvInitError>
360    where
361        C::Allocator: Default,
362    {
363        Self::from_env_in(C::Allocator::default())
364    }
365
366    /// Creates initialized database from embedded "default" database
367    ///
368    /// This is the same as consequent [`UninitDatabase::new`] and [`UninitDatabase::init_vendor`] calls
369    ///
370    /// ### Example
371    /// Using [`crate::container::BoxContainer`] as storage:
372    /// ```rust
373    /// # #[cfg(feature = "alloc")] {
374    /// # use sdecay::database::Database;
375    /// // assuming `database` feature is enabled
376    /// let database = Database::vendor();
377    /// # }
378    /// ```
379    #[cfg(feature = "database")]
380    #[inline]
381    pub fn vendor_in(allocator: C::Allocator) -> Self {
382        GenericUninitDatabase::new_in(allocator).init_vendor()
383    }
384
385    /// Same as [`Self::vendor_in`], but uses `C::Allocator`'s [`Default`] implementation to obtain the allocator
386    #[cfg(feature = "database")]
387    #[inline]
388    pub fn vendor() -> Self
389    where
390        C::Allocator: Default,
391    {
392        Self::vendor_in(C::Allocator::default())
393    }
394
395    /// Creates initialized database from embedded "min" database
396    ///
397    /// This is the same as consequent [`UninitDatabase::new`] and [`UninitDatabase::init_vendor_min`] calls
398    ///
399    /// ### Example
400    /// Using [`crate::container::BoxContainer`] as storage:
401    /// ```rust
402    /// # #[cfg(feature = "alloc")] {
403    /// # use sdecay::database::Database;
404    /// // assuming `database-min` feature is enabled
405    /// let database = Database::vendor_min();
406    /// # }
407    /// ```
408    #[cfg(feature = "database-min")]
409    #[inline]
410    pub fn vendor_min_in(allocator: C::Allocator) -> Self {
411        GenericUninitDatabase::new_in(allocator).init_vendor_min()
412    }
413
414    /// Same as [`Self::vendor_min_in`], but uses `C::Allocator`'s [`Default`] implementation to obtain the allocator
415    #[cfg(feature = "database-min")]
416    #[inline]
417    pub fn vendor_min() -> Self
418    where
419        C::Allocator: Default,
420    {
421        Self::vendor_min_in(C::Allocator::default())
422    }
423
424    /// Creates initialized database from embedded "nocoinc-min" database
425    ///
426    /// This is the same as consequent [`UninitDatabase::new`] and [`UninitDatabase::init_vendor_nocoinc_min`] calls
427    ///
428    /// ### Example
429    /// Using [`crate::container::BoxContainer`] as storage:
430    /// ```rust
431    /// # #[cfg(feature = "alloc")] {
432    /// # use sdecay::database::Database;
433    /// let database = Database::vendor_nocoinc_min();
434    /// # }
435    /// ```
436    #[cfg(feature = "database-nocoinc-min")]
437    #[inline]
438    pub fn vendor_nocoinc_min_in(allocator: C::Allocator) -> Self {
439        GenericUninitDatabase::new_in(allocator).init_vendor_nocoinc_min()
440    }
441
442    /// Same as [`Self::vendor_nocoinc_min_in`], but uses `C::Allocator`'s [`Default`] implementation to obtain the allocator
443    #[cfg(feature = "database-nocoinc-min")]
444    #[inline]
445    pub fn vendor_nocoinc_min() -> Self
446    where
447        C::Allocator: Default,
448    {
449        Self::vendor_nocoinc_min_in(C::Allocator::default())
450    }
451
452    /// Resets the database, returning it into uninitialized (empty) state
453    ///
454    /// Note, that this call **is not required** to properly drop the database - all of the resources are freed upon drop
455    ///
456    /// ### Returns
457    /// [`Option::None`] represents failure to reset the database, due to it being shared by multiple containers
458    ///
459    /// ### Example
460    /// ```rust
461    /// # #[cfg(feature = "std")] {
462    /// // database stored in the `Box` can always be reset:
463    /// # use sdecay::database::Database;
464    /// let database = Database::from_env().unwrap();
465    /// let _uninit = database.reset().expect("Should be able to reset a database behind exclusive pointer");
466    ///
467    /// // database stored in the `Arc` can be reset while not shared:
468    /// # use sdecay::database::SharedDatabase;
469    /// let shared_database = SharedDatabase::from_env().unwrap();
470    /// let _uninit = shared_database.reset().expect("Should be able to reset the database, while it is not shared");
471    ///
472    /// // once shared, databased can no longer be dropped:
473    /// let shared_database = SharedDatabase::from_env().unwrap();
474    /// let shared_database2 = shared_database.clone();
475    /// let _ = shared_database.reset().expect_err("Should not reset database in a shared state");
476    /// // once not shared, reset is possible once again:
477    /// let _uninit = shared_database2.reset().expect("Database is not longer shared, should be able to reset");
478    /// # }
479    /// ```
480    #[inline]
481    pub fn reset(mut self) -> Result<GenericUninitDatabase<C>, Self> {
482        let Some(pin) = self.0.try_inner() else {
483            return Err(self);
484        };
485        pin.reset();
486        Ok(GenericUninitDatabase(self.0))
487    }
488}
489
490impl<C: Container<Inner = SandiaDecayDataBase>> Deref for GenericDatabase<C> {
491    type Target = SandiaDecayDataBase;
492
493    #[inline]
494    fn deref(&self) -> &Self::Target {
495        &self.0
496    }
497}