hashmap_settings/stg/
mod.rs

1//! type abstraction [`Stg`] and other related elements.
2//!
3//! [`Stg`] type abstraction  
4//!
5//! [`Setting`] is the trait that needs to be implemented for types for them to be turned into `Stg`
6//!
7//! [`StgError`] Error on conversion from `Stg` or T<&Stg> into S: Setting
8//!
9//! [`StgTrait`] Trait implement
10//!
11//!
12//! # Example use of `Stg` in an [`Account`](crate::account::Account):
13//!
14//! ```rust
15//! use hashmap_settings::{account::Account,stg::{Setting,Stg,StgTrait,StgError}};
16//! //creating an account
17//! let mut account = Account::<(),&str,Stg>::default();
18//!
19//! //inserting values of distinct types
20//! account.insert("Number of trees",5.stg());
21//! account.insert("Grass color","green".to_string().stg());
22//! account.insert("Today is good",true.stg());
23//!
24//! //getting values from the account
25//! let today_bool: bool = account.get(&"Today is good").unstg()?;
26//! let grass_color: String = account.get(&"Grass color").unstg()?;
27//! let trees: i32 = account.get(&"Number of trees").unstg()?;
28//!
29//! //example of using the values
30//! print!("It's {today_bool} that today is a wonderful day,
31//!     the grass is {grass_color} and I can see {trees} trees in the distance");
32//!
33//! Ok::<(),StgError>(())
34//! ```
35
36///module containing implementations of `Setting` for rust types
37pub mod setting_implementations;
38
39use core::fmt::Debug;
40use std::any::Any;
41
42use dyn_clone::DynClone;
43use dyn_ord::DynEq;
44#[cfg(feature = "serde")]
45use serde::{Deserialize, Serialize};
46
47/// Required trait for conversion to abstract type [Stg]
48///
49/// For a Type to be able to implement Setting it needs to implement the traits
50/// [Clone], [Debug], [PartialEq] (as well as [Deserialize](https://docs.rs/serde/latest/serde/trait.Deserialize.html) and [Serialize](https://docs.rs/serde/latest/serde/trait.Serialize.html) if the "serde" feature is activated )
51///
52/// In the [future](https://github.com/OxidizedLoop/HashMapSettings/issues/1) you will be able to derive Setting,
53/// but for now you can do it by adding the following lines:
54/// ```
55/// # use hashmap_settings::stg::Setting;
56/// # #[cfg(feature = "serde")]
57/// # use serde::{Deserialize, Serialize};
58/// #
59/// # #[allow(dead_code)]
60/// # #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
61/// # #[derive(Clone, Debug, PartialEq)]
62/// # pub struct MyType{}
63/// #
64/// # #[cfg_attr(feature = "serde", typetag::serde)]
65/// // add #[typetag::serde] if serde feature is activated
66/// impl Setting for MyType{}
67/// ```
68#[cfg_attr(feature = "serde", typetag::serde(tag = "setting"))]
69pub trait Setting: Any + Debug + DynClone + DynEq {
70    /// turns a type implementing [Setting] into a [Stg]
71    ///
72    /// # Examples
73    ///
74    /// ```
75    /// use hashmap_settings::stg::{Setting,Stg};
76    /// let bool = true;
77    /// let bool_stg: Stg = bool.stg();
78    /// assert!(bool_stg == bool.stg())
79    /// ```
80    fn stg(self) -> Stg
81    where
82        Self: Setting + Sized,
83    {
84        Stg {
85            value: Box::new(self),
86        }
87    }
88}
89dyn_clone::clone_trait_object!(Setting);
90impl PartialEq for Box<dyn Setting> {
91    #[allow(clippy::unconditional_recursion)] //todo!(git issue https://github.com/rust-lang/rust-clippy/pull/12177 should resolve this)
92    fn eq(&self, other: &Self) -> bool {
93        let x: Box<dyn DynEq> = self.clone();
94        let y: Box<dyn DynEq> = other.clone();
95        x == y
96    }
97}
98
99/// type abstraction for types implementing [`Setting`]
100///
101/// Types implementing `Setting` can be turned into a `Stg` with [.stg()](Setting::stg).
102///
103/// ```
104/// use hashmap_settings::stg::{Setting,Stg};
105/// # #[allow(unused_variables)]
106/// let bool_stg: Stg = true.stg();
107/// ```
108///
109/// They can be turned back to a specific type with [.unstg()](Stg::unstg) or [.unstg_panic()](Stg::unstg_panic)
110///
111///  ```
112/// # use hashmap_settings::stg::{Setting,Stg};
113/// # let bool_stg: Stg = true.stg();
114/// # #[allow(unused_variables)]
115/// let bool: bool = bool_stg.unstg()?;
116/// # Ok::<(),Box<dyn core::any::Any>>(())
117/// ```
118///
119/// Additionally there is the [`StgTrait`] that can be implemented for types containing `Stg` to allow
120/// `.unstg()` and `.unstg_panic()` to be called on them.
121///
122/// The main example would be [Option<&Stg>]
123///
124///  ```
125/// use std::collections::HashMap;
126/// use hashmap_settings::stg::{Setting,Stg,StgError,StgTrait};
127/// let bool_stg: Stg = true.stg();
128/// let mut hashmap = HashMap::new();
129/// hashmap.insert("bool",bool_stg);
130/// # #[allow(unused_variables)]
131/// let bool: bool = hashmap.get("bool").unstg()?;
132/// # Ok::<(),StgError>(())
133/// ```
134#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
135#[derive(Clone, Debug)]
136#[must_use]
137pub struct Stg {
138    value: Box<dyn Setting>,
139}
140impl Stg {
141    /// turns a [`Stg`] into a `Result<S, Box<dyn Any>>`
142    ///
143    /// ´unstg´ is the main and safe way to used to get a concrete type `S` from `Stg`
144    ///
145    /// Consider using [`unstg_panic`](Stg::unstg_panic) if it's guaranteed that we will convert to the right type.
146    ///
147    /// # Example
148    ///
149    /// ```
150    /// use hashmap_settings::stg::{Setting,Stg};
151    ///
152    /// let bool_stg: Stg = true.stg();
153    /// assert_eq!(bool_stg.unstg::<bool>()?, true);
154    /// //we need to use ::<bool> to specify that want to turn bool_stg into a bool
155    /// # Ok::<(),Box<dyn core::any::Any>>(())
156    /// ```
157    ///
158    /// ```
159    /// use hashmap_settings::stg::{Setting,Stg};
160    ///
161    /// let bool_stg: Stg = true.stg();
162    /// let bool :bool = bool_stg.unstg()?;
163    /// // here we don't as we specific the type annotation when we use :bool
164    /// assert_eq!(bool, true);
165    /// # Ok::<(),Box<dyn core::any::Any>>(())
166    /// ```
167    ///
168    /// # Errors
169    ///
170    /// This function returns a `Err(Box<dyn Any>)` if we try to covert to the wrong type.
171    ///
172    /// ```
173    /// use hashmap_settings::stg::{Setting,Stg};
174    ///
175    /// let bool_stg: Stg = true.stg();
176    /// let number = match bool_stg.unstg::<i32>(){
177    ///     Ok(x)   => x, //unreachable!()
178    ///     Err(x)  => {
179    ///         print!("wrong conversion {:?}",x);
180    ///         404
181    ///     },
182    /// };
183    /// assert_eq!(number, 404)
184    /// ```
185    pub fn unstg<S: Setting>(self) -> Result<S, Box<dyn Any>> {
186        let x: Box<dyn Any> = self.value;
187        x.downcast().map(|t| *t)
188    }
189    /// turns a [`Stg`] into a concrete type `S`, can [`panic!`]
190    ///
191    /// This method is used to get a concrete type out of a `Stg`
192    /// when it's know what `S` it contains.
193    ///
194    /// # Panics
195    ///
196    /// We need to be careful using `unstg_panic` as if we try convert to a type
197    /// that isn't the one contained in `Stg` the program will panic.
198    /// Consider using [`unstg`](Stg::unstg) as it returns a result type instead.
199    ///
200    /// ```should_panic
201    /// use hashmap_settings::stg::{Setting,Stg};
202    ///
203    /// let bool_stg: Stg = true.stg();
204    /// let _number :i32 = bool_stg.unstg_panic();
205    /// // this panics, as the Box<dyn Setting> holds a bool value but we are trying to convert it to a i32
206    /// ```
207    /// # Examples
208    ///
209    /// ```
210    /// use hashmap_settings::stg::{Setting,Stg};
211    ///
212    /// let bool_stg: Stg = true.stg();
213    /// assert_eq!(bool_stg.unstg_panic::<bool>(), true);
214    /// //we need to use ::<bool> to specify that want to turn bool_stg into a bool
215    /// ```
216    /// ```
217    /// use hashmap_settings::stg::{Setting,Stg};
218    ///
219    /// let bool_stg: Stg = true.stg();
220    /// let bool :bool = bool_stg.unstg_panic();
221    /// // here we don't as we specific the type annotation when we use :bool
222    /// assert_eq!(bool, true);
223    /// ```
224    #[must_use]
225    pub fn unstg_panic<S: Setting>(self) -> S {
226        let x: Box<dyn Any> = self.value;
227        *x.downcast().unwrap()
228    }
229}
230#[cfg_attr(feature = "serde", typetag::serde)]
231impl Setting for Stg {}
232impl PartialEq for Stg {
233    fn eq(&self, other: &Self) -> bool {
234        self.value == other.value.clone()
235    }
236}
237impl StgTrait for Option<&Stg> {
238    fn unstg<S: Setting>(self) -> Result<S, StgError> {
239        self.map_or(Err(StgError::None), |value| {
240            match value.clone().unstg::<S>() {
241                Ok(value) => Ok(value),
242                Err(_error) => Err(StgError::WrongType),
243            }
244        })
245    }
246    fn unstg_panic<S: Setting>(self) -> S {
247        self.unwrap().clone().unstg_panic()
248    }
249}
250
251/// [`Stg`] container converter trait
252///
253/// This trait is implemented by types to facilitate the conversion from
254/// `T`<`Stg`> to a concrete type `S`.
255///
256/// Main example, and the use case of this crate, would be `Option<&Stg>` as it is what gets
257/// returned when calling `get` on an `HashMap`/`Account`
258///
259/// #Example
260/// ```
261/// # use hashmap_settings::{account::Account,stg::{Setting,Stg,StgTrait,StgError}};
262///
263/// //creating a Stg Account
264/// let mut account = Account::<(), &str, Stg>::default();
265///
266/// //inserting values of distinct types
267/// account.insert("Number of trees", 5.stg());
268/// account.insert("Grass color", "green".to_string().stg());
269/// account.insert("Today is good", true.stg());
270///
271/// //getting values from the account in 3 different ways
272/// let today_bool: bool    = account.get(&"Today is good").unstg()?;
273/// let grass_color: String = account.get(&"Grass color").unstg_panic();
274/// let trees: i32          = account.get(&"Number of trees").unwrap().clone().unstg().unwrap();
275/// //in the i32 example the last unwrap could be swapped for a "?" but it still would be a
276/// //more complicated method chain than the other two alternatives.
277///
278/// //example of using the values
279/// print!("It's {today_bool} that today is a wonderful day, the grass
280///     is {grass_color} and I see {trees} trees in the distance");
281/// # Ok::<(), StgError>(())
282/// ```
283///
284#[allow(clippy::module_name_repetitions)]
285pub trait StgTrait {
286    /// Conversion to a Result<S, StgError>.
287    ///
288    /// Will return a [StgError] when the value isn't found, or when the value is found
289    /// but isn't of the type that it is being converted to.
290    ///
291    /// # Errors
292    ///
293    /// This function can return [StgErrors](StgError).
294    ///
295    /// [None](StgError::None) when the value is not contained in the `T<Stg>`.
296    /// [WrongType][StgError::WrongType] when the value is contained, but it was been tried
297    /// to convert it to the wrong type
298    ///
299    /// # Examples
300    ///
301    /// ```
302    /// # use hashmap_settings::{account::Account,stg::{Setting,Stg,StgTrait,StgError}};
303    /// let mut account: Account<(),&str,Stg> = Default::default();
304    /// account.insert("a small number", 42_i32.stg());
305    /// assert_eq!(account.get(&"a small number").unstg::<i32>(), Ok(42));
306    /// assert_eq!(account.get(&"a big number").unstg::<i32>(), Err(StgError::None));
307    /// assert_eq!(account.get(&"a small number").unstg::<String>(), Err(StgError::WrongType));
308    /// ```
309    fn unstg<S: Setting>(self) -> Result<S, StgError>;
310    /// Conversion to concrete type `S`, can panic.
311    ///
312    /// in the case the conversion can't be made, this method should panic.
313    /// but this method should never be used if the conversion is not assured to be the correct one
314    /// and [`unstg`](StgTrait::unstg) should be used instead.
315    ///
316    /// # Examples
317    ///
318    /// ```
319    /// # use hashmap_settings::{account::Account,stg::{Setting,Stg,StgTrait}};
320    /// let mut account: Account<(),&str,Stg> = Default::default();
321    /// account.insert("a small number", 42_i32.stg());
322    /// assert_eq!(account.get(&"a small number").unstg_panic::<i32>(), 42);
323    /// ```
324    /// ```should_panic
325    /// # use hashmap_settings::{account::Account,stg::{Setting,Stg,StgTrait}};
326    /// let mut account: Account<(),&str,Stg> = Default::default();
327    /// account.insert("a small number", 42_i32.stg());
328    /// assert_eq!(account.get(&"a small number").unstg_panic::<bool>(), true);//this panics
329    /// ```
330    #[must_use]
331    fn unstg_panic<S: Setting>(self) -> S;
332}
333
334/// Errors for [Stg] and [StgTrait] methods
335#[derive(Debug, PartialEq, Eq)]
336#[allow(clippy::module_name_repetitions)]
337pub enum StgError {
338    /// No value found, equivalent to None in Option()
339    None,
340    /// Error of trying to convert to the wrong type,
341    WrongType, //todo!() change WrongType to contain the error Err(StgError::WrongType(Box<dyn core::any::Any>)),
342}