libhaystack/haystack/val/
dict.rs

1// Copyright (C) 2020 - 2022, J2 Innovations
2
3//! Haystack Dict
4
5use crate::{dict_get, dict_has};
6
7use crate::haystack::val::*;
8use std::borrow::Cow;
9use std::collections::BTreeMap;
10use std::fmt::{Debug, Display, Formatter};
11use std::hash::Hash;
12use std::iter::{FromIterator, Iterator};
13use std::ops::{Deref, DerefMut};
14
15// Alias for the underlying Dict type
16pub type DictType = BTreeMap<String, Value>;
17
18/// A Haystack Dictionary
19///
20/// Uses a [BTreeMap<String, Value>](std::collections::BTreeMap) for the back-store
21///
22/// # Example
23/// Create a dictionary value
24/// ```
25/// use libhaystack::*;
26/// use libhaystack::val::*;
27///
28/// let dict = Value::from(dict! {
29///        "site" => Value::make_marker(),
30///        "name" => Value::make_str("Foo")
31///    });
32/// assert!(dict.is_dict());
33///
34/// // Get the Dict value
35/// let dict_value = Dict::try_from(&dict).unwrap();
36/// assert!(!dict_value.is_empty());
37/// assert!(dict_value.has("site"));
38///
39/// // Get a `Str` value from the dictionary
40/// assert_eq!(dict_value.get_str("name"), Some(&"Foo".into()));
41///```
42#[derive(Eq, PartialEq, Hash, Clone, Debug, Default)]
43pub struct Dict {
44    value: DictType,
45}
46
47/// Dictionary trait with utilities that help working with
48/// the haystack Dict types.
49pub trait HaystackDict {
50    /// Get the optional `id` of this `Dict`
51    fn id(&self) -> Option<&Ref>;
52
53    /// Get the `id` Ref of this `Dict`, or a default Ref if the id is not present
54    fn safe_id(&self) -> Ref;
55
56    /// Get the optional `mod` of this `Dict`.
57    /// On record `Dict`s this represents the last time this
58    /// record has been changed, or the time it was created.
59    fn ts(&self) -> Option<&DateTime>;
60
61    /// True if Dict contains the key
62    fn has(&self, key: &str) -> bool;
63
64    /// True if key is not found
65    fn missing(&self, key: &str) -> bool;
66
67    /// True if key exists and is a Marker
68    fn has_marker(&self, key: &str) -> bool;
69
70    /// True if key exists and is a Na
71    fn has_na(&self, key: &str) -> bool;
72
73    /// True if key exists and is a Remove
74    fn has_remove(&self, key: &str) -> bool;
75
76    /// Get optional Bool for the key
77    fn get_bool<'a>(&'a self, key: &str) -> Option<&'a Bool>;
78
79    /// Get optional Number for the key
80    fn get_num<'a>(&'a self, key: &str) -> Option<&'a Number>;
81
82    /// Get optional Ref for the key
83    fn get_ref<'a>(&'a self, key: &str) -> Option<&'a Ref>;
84
85    /// Get optional Str for the key
86    fn get_str<'a>(&'a self, key: &str) -> Option<&'a Str>;
87
88    /// Get optional XStr for the key
89    fn get_xstr<'a>(&'a self, key: &str) -> Option<&'a XStr>;
90
91    /// Get optional Uri for the key
92    fn get_uri<'a>(&'a self, key: &str) -> Option<&'a Uri>;
93
94    /// Get optional Symbol for the key
95    fn get_symbol<'a>(&'a self, key: &str) -> Option<&'a Symbol>;
96
97    /// Get optional Date for the key
98    fn get_date<'a>(&'a self, key: &str) -> Option<&'a Date>;
99
100    /// Get optional Time for the key
101    fn get_time<'a>(&'a self, key: &str) -> Option<&'a Time>;
102
103    /// Get optional DateTime for the key
104    fn get_date_time<'a>(&'a self, key: &str) -> Option<&'a DateTime>;
105
106    /// Get optional Coord for the key
107    fn get_coord<'a>(&'a self, key: &str) -> Option<&'a Coord>;
108
109    /// Get optional Dict for the key
110    fn get_dict<'a>(&'a self, key: &str) -> Option<&'a Dict>;
111
112    /// Get optional List for the key
113    fn get_list<'a>(&'a self, key: &str) -> Option<&'a List>;
114
115    /// Get optional Grid for the key
116    fn get_grid<'a>(&'a self, key: &str) -> Option<&'a Grid>;
117
118    /// Get a formatted display string for a dict.
119    fn dis(&self) -> Cow<'_, str>;
120}
121
122impl Dict {
123    /// Construct a new `Dict`
124    pub fn new() -> Dict {
125        Dict {
126            value: DictType::new(),
127        }
128    }
129}
130
131/// Implement FromIterator for `Dict`
132///
133/// Allows constructing a `Dict` from a `(String, Value)` tuple iterator
134impl FromIterator<(String, Value)> for Dict {
135    fn from_iter<T: IntoIterator<Item = (String, Value)>>(iter: T) -> Self {
136        Dict {
137            value: DictType::from_iter(iter),
138        }
139    }
140}
141
142/// Proxy method calls to the `Dict`'s `value` member
143impl Deref for Dict {
144    type Target = DictType;
145    #[inline]
146    fn deref(&self) -> &Self::Target {
147        &self.value
148    }
149}
150
151/// Proxy method calls to the mutable `Dict`'s `value` member
152impl DerefMut for Dict {
153    #[inline]
154    fn deref_mut(&mut self) -> &mut DictType {
155        &mut self.value
156    }
157}
158
159#[allow(clippy::non_canonical_partial_ord_impl)]
160impl PartialOrd for Dict {
161    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
162        self.value.partial_cmp(&other.value)
163    }
164}
165
166impl Ord for Dict {
167    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
168        if self.is_empty() && other.is_empty() {
169            std::cmp::Ordering::Equal
170        } else {
171            let keys_cmp = self.value.keys().cmp(other.value.keys());
172            if keys_cmp == std::cmp::Ordering::Equal {
173                self.value.values().cmp(other.value.values())
174            } else {
175                keys_cmp
176            }
177        }
178    }
179}
180
181impl HaystackDict for Dict {
182    fn id(&self) -> Option<&Ref> {
183        self.get_ref("id")
184    }
185
186    fn safe_id(&self) -> Ref {
187        self.get_ref("id").map_or(Ref::default(), |id| id.clone())
188    }
189
190    fn ts(&self) -> Option<&DateTime> {
191        self.get_date_time("mod")
192    }
193
194    fn has(&self, key: &str) -> bool {
195        self.contains_key(key)
196    }
197
198    fn missing(&self, key: &str) -> bool {
199        !self.has(key)
200    }
201
202    fn has_marker(&self, key: &str) -> bool {
203        dict_has! {self, key, Marker}
204    }
205
206    fn has_na(&self, key: &str) -> bool {
207        dict_has! {self, key, Na}
208    }
209
210    fn has_remove(&self, key: &str) -> bool {
211        dict_has! {self, key, Remove}
212    }
213
214    fn get_bool<'a>(&'a self, key: &str) -> Option<&'a Bool> {
215        dict_get! {self, key, Bool}
216    }
217
218    fn get_num<'a>(&'a self, key: &str) -> Option<&'a Number> {
219        dict_get! {self, key, Number}
220    }
221
222    fn get_str<'a>(&'a self, key: &str) -> Option<&'a Str> {
223        dict_get! {self, key, Str}
224    }
225
226    fn get_xstr<'a>(&'a self, key: &str) -> Option<&'a XStr> {
227        dict_get! {self, key, XStr}
228    }
229
230    fn get_ref<'a>(&'a self, key: &str) -> Option<&'a Ref> {
231        dict_get! {self, key, Ref}
232    }
233
234    fn get_uri<'a>(&'a self, key: &str) -> Option<&'a Uri> {
235        dict_get! {self, key, Uri}
236    }
237
238    fn get_symbol<'a>(&'a self, key: &str) -> Option<&'a Symbol> {
239        dict_get! {self, key, Symbol}
240    }
241
242    fn get_date<'a>(&'a self, key: &str) -> Option<&'a Date> {
243        dict_get! {self, key, Date}
244    }
245
246    fn get_time<'a>(&'a self, key: &str) -> Option<&'a Time> {
247        dict_get! {self, key, Time}
248    }
249
250    fn get_date_time<'a>(&'a self, key: &str) -> Option<&'a DateTime> {
251        dict_get! {self, key, DateTime}
252    }
253
254    fn get_coord<'a>(&'a self, key: &str) -> Option<&'a Coord> {
255        dict_get! {self, key, Coord}
256    }
257
258    fn get_dict<'a>(&'a self, key: &str) -> Option<&'a Dict> {
259        dict_get! {self, key, Dict}
260    }
261
262    fn get_list<'a>(&'a self, key: &str) -> Option<&'a List> {
263        dict_get! {self, key, List}
264    }
265
266    fn get_grid<'a>(&'a self, key: &str) -> Option<&'a Grid> {
267        dict_get! {self, key, Grid}
268    }
269
270    fn dis(&self) -> Cow<'_, str> {
271        dict_to_dis(self, &|_| None, None)
272    }
273}
274
275/// Converts from `DictType` to a `Dict`
276impl From<DictType> for Dict {
277    fn from(from: DictType) -> Self {
278        Dict { value: from }
279    }
280}
281
282/// Converts from `DictType` to a `Dict` `Value`
283impl From<DictType> for Value {
284    fn from(from: DictType) -> Self {
285        Value::from(Dict { value: from })
286    }
287}
288
289/// Converts from `Dict` to a `Dict` `Value`
290impl From<Dict> for Value {
291    fn from(value: Dict) -> Self {
292        Value::Dict(value)
293    }
294}
295
296/// Tries to convert from `Value` to a `Dict`
297impl TryFrom<&Value> for Dict {
298    type Error = &'static str;
299    fn try_from(value: &Value) -> Result<Self, Self::Error> {
300        match value {
301            Value::Dict(v) => Ok(v.clone()),
302            _ => Err("Value is not an `Dict`"),
303        }
304    }
305}
306
307/// Pretty print this
308impl Display for Dict {
309    fn fmt(&self, f: &mut Formatter) -> std::fmt::Result {
310        Debug::fmt(&self.value, f)
311    }
312}
313
314/// A macro for creating a [Dict](crate::val::Dict) from literals
315///
316/// # Example
317/// ```
318///  use libhaystack::*;
319///  use libhaystack::val::*;
320///     let dict = dict!{
321///         "site" => Value::make_marker(),
322///         "dis" => Value::make_str("Some site")
323///     };
324/// ```
325///
326#[macro_export]
327macro_rules! dict(
328    { $($key:expr => $value:expr),* $(,)? } => {
329        {
330            let mut map = ::std::collections::BTreeMap::new();
331            $(
332                map.insert(String::from($key), $value);
333            )+
334            Dict::from(map)
335        }
336     };
337);
338
339/// A macro for retrieving a type from a [Dict](crate::val::Dict) by a key
340///
341/// This is a private API, consider using the [Dict](crate::val::Dict) specialized functions for
342/// getting the values.
343///
344#[macro_export]
345macro_rules! dict_get(
346    { $self:ident, $key:expr, $type:ident } => {
347        {
348            if let Some(value) = $self.get($key) {
349                match value {
350                    Value::$type(val) => Some(&val),
351                    _ => None,
352                }
353            } else {
354                None
355            }
356    }
357     };
358);
359
360/// A macro for determining if [Dict](crate::val::Dict) has a type for the key
361///
362/// Private API, use the [Dict](crate::val::Dict) specialized functions
363///
364#[macro_export]
365macro_rules! dict_has(
366    { $self:ident, $key:expr, $type:ident } => {
367        {
368            let entry = $self.get($key);
369            matches!(entry, Some(Value::$type))
370        }
371     };
372);
373
374/// Convert a dict to its formatted display string.
375pub fn dict_to_dis<'a, GetLocalizedFunc>(
376    dict: &'a Dict,
377    get_localized: &'a GetLocalizedFunc,
378    def: Option<Cow<'a, str>>,
379) -> Cow<'a, str>
380where
381    GetLocalizedFunc: Fn(&str) -> Option<Cow<'a, str>>,
382{
383    if let Some(val) = dict.get("dis") {
384        return decode_str_from_value(val);
385    }
386
387    if let Some(val) = dict.get("disMacro") {
388        return if let Value::Str(val) = val {
389            dis_macro(
390                &val.value,
391                |val| dict.get(val).map(Cow::Borrowed),
392                get_localized,
393            )
394        } else {
395            decode_str_from_value(val)
396        };
397    }
398
399    if let Some(val) = dict.get("disKey") {
400        if let Value::Str(val_str) = val {
401            if let Some(val_str) = get_localized(&val_str.value) {
402                return val_str;
403            }
404        }
405        return decode_str_from_value(val);
406    }
407
408    if let Some(val) = dict.get("name") {
409        return decode_str_from_value(val);
410    }
411
412    if let Some(val) = dict.get("def") {
413        return decode_str_from_value(val);
414    }
415
416    if let Some(val) = dict.get("tag") {
417        return decode_str_from_value(val);
418    }
419
420    if let Some(val) = dict.get("navName") {
421        return decode_str_from_value(val);
422    }
423
424    if let Some(val) = dict.get("id") {
425        return if let Value::Ref(val) = val {
426            Cow::Borrowed(val.dis.as_ref().unwrap_or(&val.value))
427        } else {
428            decode_str_from_value(val)
429        };
430    }
431
432    def.unwrap_or(Cow::Borrowed(""))
433}
434
435fn decode_str_from_value(val: &'_ Value) -> Cow<'_, str> {
436    match val {
437        Value::Str(val) => Cow::Borrowed(&val.value),
438        _ => Cow::Owned(val.to_string()),
439    }
440}
441
442#[cfg(test)]
443mod test {
444    use std::borrow::Cow;
445
446    use crate::val::{dict_to_dis, Dict, HaystackDict, Value};
447
448    fn get_localized<'a>(key: &str) -> Option<Cow<'a, str>> {
449        match key {
450            "key" => Some(Cow::Borrowed("translated")),
451            _ => None,
452        }
453    }
454
455    #[test]
456    fn dict_to_dis_returns_dis() {
457        let dict = dict!["dis" => Value::make_str("display")];
458        assert_eq!(dict_to_dis(&dict, &|_| None, None), "display");
459    }
460
461    #[test]
462    fn dict_to_dis_returns_dis_not_str() {
463        let dict = dict!["dis" => Value::make_ref("display")];
464        assert_eq!(dict_to_dis(&dict, &|_| None, None), "@display");
465    }
466
467    #[test]
468    fn dict_to_dis_returns_dis_macro() {
469        let dict = dict!["foo" => Value::make_str("bar"), "disMacro" => Value::make_str("hello $foo world!")];
470        assert_eq!(dict_to_dis(&dict, &|_| None, None), "hello bar world!");
471    }
472
473    #[test]
474    fn dict_to_dis_returns_dis_key_translated() {
475        let dict = dict!["foo" => Value::make_str("bar"), "disKey" => Value::make_str("key")];
476        assert_eq!(dict_to_dis(&dict, &get_localized, None), "translated");
477    }
478
479    #[test]
480    fn dict_to_dis_returns_dis_key_not_translated() {
481        let dict =
482            dict!["foo" => Value::make_str("bar"), "disKey" => Value::make_str("notTranslated")];
483        assert_eq!(dict_to_dis(&dict, &get_localized, None), "notTranslated");
484    }
485
486    #[test]
487    fn dict_to_dis_returns_name() {
488        let dict = dict!["name" => Value::make_str("display")];
489        assert_eq!(dict_to_dis(&dict, &|_| None, None), "display");
490    }
491
492    #[test]
493    fn dict_to_dis_returns_def() {
494        let dict = dict!["def" => Value::make_str("display")];
495        assert_eq!(dict_to_dis(&dict, &|_| None, None), "display");
496    }
497
498    #[test]
499    fn dict_to_dis_returns_tag() {
500        let dict = dict!["tag" => Value::make_str("display")];
501        assert_eq!(dict_to_dis(&dict, &|_| None, None), "display");
502    }
503
504    #[test]
505    fn dict_to_dis_returns_nav_name() {
506        let dict = dict!["navName" => Value::make_str("navName")];
507        assert_eq!(dict_to_dis(&dict, &|_| None, None), "navName");
508    }
509
510    #[test]
511    fn dict_to_dis_returns_id() {
512        let dict = dict!["id" => Value::make_ref("id")];
513        assert_eq!(dict_to_dis(&dict, &|_| None, None), "id");
514    }
515
516    #[test]
517    fn dict_to_dis_returns_id_dis() {
518        let dict = dict!["id" => Value::make_ref_with_dis("id", "dis")];
519        assert_eq!(dict_to_dis(&dict, &|_| None, None), "dis");
520    }
521
522    #[test]
523    fn dict_returns_dis() {
524        let dict = dict!["dis" => Value::make_str("display")];
525        assert_eq!(dict.dis(), "display");
526    }
527
528    #[test]
529    fn dict_returns_default_value_if_none_found() {
530        let dict = dict!["something" => Value::make_str("display")];
531        assert_eq!(
532            dict_to_dis(&dict, &|_| None, Some("default".into())),
533            "default"
534        );
535    }
536}