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}