xapi_rs/data/
language_map.rs

1// SPDX-License-Identifier: GPL-3.0-or-later
2
3use crate::data::{Canonical, DataError, MultiLingual, MyLanguageTag};
4use core::fmt;
5use serde::{Deserialize, Serialize};
6use std::{
7    collections::{btree_map::Keys, BTreeMap},
8    mem,
9};
10
11#[doc = include_str!("../../doc/LanguageMap.md")]
12#[derive(Clone, Debug, Default, Eq, PartialEq, Deserialize, Serialize)]
13pub struct LanguageMap(BTreeMap<MyLanguageTag, String>);
14
15/// The empty [LanguageMap] singleton.
16pub const EMPTY_LANGUAGE_MAP: LanguageMap = LanguageMap(BTreeMap::new());
17
18impl LanguageMap {
19    /// Create an empty [LanguageMap] instance.
20    pub fn new() -> Self {
21        LanguageMap(BTreeMap::new())
22    }
23
24    /// Return the number of entries in this dictionary.
25    pub fn len(&self) -> usize {
26        self.0.len()
27    }
28
29    /// Return a reference to the label keyed by `k` if it exists, or `None`
30    /// otherwise.
31    pub fn get(&self, k: &MyLanguageTag) -> Option<&str> {
32        self.0.get(k).map(|x| x.as_str())
33    }
34
35    /// Return TRUE if this dictionary is empty; FALSE otherwise.
36    pub fn is_empty(&self) -> bool {
37        self.0.is_empty()
38    }
39
40    /// Move all elements from `other` into self, leaving `other` empty.
41    pub fn append(&mut self, other: &mut Self) {
42        if other.is_empty() {
43            return;
44        }
45
46        if self.is_empty() {
47            mem::swap(self, other);
48            return;
49        }
50
51        self.0.append(&mut other.0)
52    }
53
54    /// Insert `v` keyed by `k` and return the previous `v` if `k` was already
55    /// known, or `None` otherwise.
56    pub fn insert(&mut self, k: &MyLanguageTag, v: &str) -> Option<String> {
57        self.0.insert(k.to_owned(), v.to_owned())
58    }
59
60    /// Return an iterator over this dictionary's keys.
61    pub fn keys(&self) -> Keys<'_, MyLanguageTag, String> {
62        self.0.keys()
63    }
64
65    /// Return TRUE if `k` is a known key of this dictionary; FALSE otherwise.
66    pub fn contains_key(&self, k: &MyLanguageTag) -> bool {
67        self.0.contains_key(k)
68    }
69
70    /// Retain entries in this that satisfy the given predicate.
71    pub fn retain<F>(&mut self, mut f: F)
72    where
73        F: FnMut(&MyLanguageTag, &mut String) -> bool,
74    {
75        self.0.retain(|k, v| f(k, v))
76    }
77
78    /// Extend this w/ the contents of `other` without modifying the latter.
79    pub fn extend(&mut self, other: Self) {
80        self.0.extend(other.0)
81    }
82}
83
84impl fmt::Display for LanguageMap {
85    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
86        write!(f, "{}", serde_json::to_string(self).unwrap())
87    }
88}
89
90impl MultiLingual for LanguageMap {
91    fn add_label(&mut self, tag: &MyLanguageTag, label: &str) -> Result<&mut Self, DataError> {
92        self.insert(tag, label);
93
94        Ok(self)
95    }
96}
97
98impl Canonical for LanguageMap {
99    fn canonicalize(&mut self, tags: &[MyLanguageTag]) {
100        if !self.is_empty() {
101            if !tags.is_empty() {
102                for tag in tags {
103                    if self.contains_key(tag) {
104                        // retain entry for this key...
105                        self.retain(|k, _| k == tag);
106                        return;
107                    }
108                }
109                // if we're still here then we found no common tag...
110            }
111            // pick a random entry... but only if the map contains more than 1...
112            if self.len() > 1 {
113                let t = self.keys().next().unwrap().clone();
114                self.retain(|k, _| k == t)
115            }
116        }
117    }
118}
119
120#[cfg(test)]
121mod tests {
122    use super::*;
123    use crate::DataError;
124    use std::str::FromStr;
125    use tracing_test::traced_test;
126
127    #[test]
128    fn test_und_langtag() -> Result<(), DataError> {
129        let _ = MyLanguageTag::from_str("und")?;
130
131        Ok(())
132    }
133
134    #[traced_test]
135    #[test]
136    fn test_multilingual_trait() -> Result<(), DataError> {
137        let en = MyLanguageTag::from_str("en")?;
138        let de = MyLanguageTag::from_str("de")?;
139
140        let mut lm = LanguageMap::new();
141        lm.add_label(&en, "Good morning").unwrap();
142        lm.add_label(&de, "Gutten morgen").unwrap();
143        assert_eq!(lm.len(), 2);
144
145        lm.add_label(&de, "Gutten tag").unwrap();
146        assert_eq!(lm.len(), 2);
147
148        Ok(())
149    }
150
151    #[traced_test]
152    #[test]
153    fn test_canonicalize_trait() -> Result<(), DataError> {
154        let en = MyLanguageTag::from_str("en")?;
155        let de = MyLanguageTag::from_str("de")?;
156        let fr = MyLanguageTag::from_str("fr")?;
157
158        let language_tags = &[
159            MyLanguageTag::from_str("en-AU")?,
160            MyLanguageTag::from_str("en-US")?,
161            MyLanguageTag::from_str("en-GB")?,
162            en.clone(),
163        ];
164
165        let mut lm = LanguageMap::new();
166        lm.insert(&fr, "larry");
167        lm.insert(&en, "curly");
168        lm.insert(&de, "moe");
169        assert_eq!(lm.len(), 3);
170
171        lm.canonicalize(language_tags);
172
173        assert_eq!(lm.len(), 1);
174        assert_eq!(lm.get(&en).unwrap(), "curly");
175
176        Ok(())
177    }
178
179    #[traced_test]
180    #[test]
181    fn test_bad_json() {
182        const JSON: &str = r#"{"a12345678":"should error"}"#;
183
184        let res = serde_json::from_str::<LanguageMap>(JSON);
185        assert!(res.is_err());
186    }
187}