languages_rs/value.rs
1//! The Value enum, a loosely typed way of representing any valid language text value.
2//!
3//! # Valid language texts
4//! Language texts only can be in JSON or TOML format.
5//!
6//! ## JSON
7//! ```json
8//! {
9//! "hello_world": "Hello, world!",
10//! "pages": {
11//! "home": {
12//! "title": "Home page",
13//! "description": "This is the home page."
14//! },
15//! },
16//! "data": [
17//! "Message 1",
18//! "Message 2"
19//! ]
20//! }
21//! ```
22//!
23//! ## TOML
24//! ```toml
25//! hello_world = "Hello, world!"
26//!
27//! [pages]
28//! [pages.home]
29//! title = "Home page"
30//! description = "This is the home page."
31//!
32//! data = [
33//! "Message 1",
34//! "Message 2"
35//! ]
36//! ```
37
38use std::{collections::HashMap, fmt};
39
40#[cfg(feature = "with-json")]
41use serde_json::Value as JsonValue;
42
43#[cfg(feature = "with-toml")]
44use toml::Value as TomlValue;
45
46#[derive(Clone, Debug, PartialEq)]
47pub enum Value {
48 String(String),
49 Array(Vec<Value>),
50 Object(HashMap<String, Value>),
51}
52
53impl Value {
54 #[cfg(feature = "with-json")]
55 fn from_value(value: JsonValue) -> anyhow::Result<Self> {
56 if value.is_string() {
57 return Ok(Self::String(String::from(value.as_str().unwrap())));
58 } else if value.is_array() {
59 return Ok(Self::Array(
60 value
61 .as_array()
62 .unwrap()
63 .iter()
64 .map(|e| Self::from_value(e.clone()).expect("Invalid format."))
65 .collect(),
66 ));
67 } else if value.is_object() {
68 let mut new_data: HashMap<String, Value> = HashMap::new();
69 for (key, value) in value.as_object().unwrap().iter() {
70 new_data.insert(key.clone(), Self::from_value(value.clone())?);
71 }
72
73 return Ok(Self::Object(new_data));
74 }
75
76 Err(anyhow::Error::msg(format!(
77 "Cannot parse `{}` as a language text value.",
78 value
79 )))
80 }
81
82 #[cfg(feature = "with-toml")]
83 pub fn from_value(value: TomlValue) -> anyhow::Result<Self> {
84 if value.is_str() {
85 return Ok(Self::String(String::from(value.as_str().unwrap())));
86 } else if value.is_array() {
87 return Ok(Self::Array(
88 value
89 .as_array()
90 .unwrap()
91 .iter()
92 .map(|e| Self::from_value(e.clone()).expect("Invalid format."))
93 .collect(),
94 ));
95 } else if value.is_table() {
96 let mut new_data: HashMap<String, Value> = HashMap::new();
97 for (key, value) in value.as_table().unwrap().iter() {
98 new_data.insert(key.clone(), Self::from_value(value.clone())?);
99 }
100
101 return Ok(Self::Object(new_data));
102 }
103
104 Err(anyhow::Error::msg(format!(
105 "Cannot parse `{}` as a language text value.",
106 value
107 )))
108 }
109
110 /// Get the texts from a JSON string.
111 ///
112 /// # Example
113 /// ```rust
114 /// use languages_rs::Value;
115 ///
116 /// let value = Value::from_string(String::from("\"Hi\""));
117 /// assert!(value.is_ok());
118 /// assert_eq!(value.unwrap(), Value::String(String::from("Hi")));
119 /// ```
120 #[cfg(feature = "with-json")]
121 pub fn from_string(text: String) -> anyhow::Result<Self> {
122 Self::from_value(serde_json::from_str(&text)?)
123 }
124
125 /// Get the texts from a JSON string or TOML string.
126 ///
127 /// # Example
128 /// ```rust
129 /// use languages_rs::Value;
130 ///
131 /// use std::collections::HashMap;
132 ///
133 /// let value = Value::from_string(String::from("hi = \"Hi\""));
134 /// assert!(value.is_ok());
135 ///
136 /// let mut data: HashMap<String, Value> = HashMap::new();
137 /// data.insert(String::from("hi"), Value::String(String::from("Hi")));
138 ///
139 /// assert_eq!(value.unwrap(), Value::Object(data));
140 /// ```
141 #[cfg(feature = "with-toml")]
142 pub fn from_string(text: String) -> anyhow::Result<Self> {
143 Self::from_value(text.parse()?)
144 }
145
146 #[cfg(all(not(feature = "with-json"), not(feature = "with-toml")))]
147 pub fn from_string(_text: String) -> anyhow::Result<Self> {
148 Err(anyhow::Error::msg("You must define the parse feature."))
149 }
150
151 /// Check if the current value is a string.
152 ///
153 /// # Example
154 /// ```rust
155 /// use languages_rs::Value;
156 ///
157 /// #[cfg(feature = "with-json")]
158 /// fn main() {
159 /// let value = Value::from_string(String::from("\"Hi\""));
160 /// assert!(value.is_ok());
161 /// assert!(value.unwrap().is_string());
162 /// }
163 ///
164 /// #[cfg(feature = "with-toml")]
165 /// fn main() {
166 /// let value = Value::from_string(String::from("hi = \"Hi\""));
167 /// assert!(value.is_ok());
168 ///
169 /// let table = value.unwrap().get_object();
170 /// assert!(table.is_some());
171 ///
172 /// let table = table.unwrap();
173 /// let text = table.get("hi");
174 /// assert!(text.is_some());
175 /// assert!(text.unwrap().is_string());
176 /// }
177 ///
178 /// #[cfg(all(not(feature = "with-json"), not(feature = "with-toml")))]
179 /// fn main() {}
180 /// ```
181 pub fn is_string(&self) -> bool {
182 matches!(self, Self::String(_))
183 }
184
185 /// Get the string value.
186 ///
187 /// # Example
188 /// ```rust
189 /// use languages_rs::Value;
190 ///
191 /// #[cfg(feature = "with-json")]
192 /// fn main() {
193 /// let value = Value::from_string(String::from("\"Hi\""));
194 /// assert!(value.is_ok());
195 /// assert_eq!(value.unwrap().get_string(), Some(String::from("Hi")));
196 /// }
197 ///
198 /// #[cfg(feature = "with-toml")]
199 /// fn main() {
200 /// let value = Value::from_string(String::from("hi = \"Hi\""));
201 /// assert!(value.is_ok());
202 ///
203 /// let table = value.unwrap().get_object();
204 /// assert!(table.is_some());
205 ///
206 /// let table = table.unwrap();
207 /// let text = table.get("hi");
208 /// assert!(text.is_some());
209 /// assert_eq!(text.unwrap().get_string(), Some(String::from("Hi")));
210 /// }
211 ///
212 /// #[cfg(all(not(feature = "with-json"), not(feature = "with-toml")))]
213 /// fn main() {}
214 /// ```
215 pub fn get_string(&self) -> Option<String> {
216 match self {
217 Self::String(value) => Some(value.clone()),
218 _ => None,
219 }
220 }
221
222 /// Check if the current value is an array.
223 ///
224 /// # Example
225 /// ```rust
226 /// use languages_rs::Value;
227 ///
228 /// #[cfg(feature = "with-json")]
229 /// fn main() {
230 /// let value = Value::from_string(String::from("[\"1\", \"2\"]"));
231 /// assert!(value.is_ok());
232 /// assert!(value.unwrap().is_array());
233 /// }
234 ///
235 /// #[cfg(feature = "with-toml")]
236 /// fn main() {
237 /// let value = Value::from_string(String::from("numbers = [\"1\", \"2\"]"));
238 /// assert!(value.is_ok());
239 ///
240 /// let table = value.unwrap().get_object();
241 /// assert!(table.is_some());
242 ///
243 /// let table = table.unwrap();
244 /// let values = table.get("numbers");
245 /// assert!(values.is_some());
246 /// assert!(values.unwrap().is_array());
247 /// }
248 ///
249 /// #[cfg(all(not(feature = "with-json"), not(feature = "with-toml")))]
250 /// fn main() {}
251 /// ```
252 pub fn is_array(&self) -> bool {
253 matches!(self, Self::Array(_))
254 }
255
256 /// Get the array value.
257 ///
258 /// # Example
259 /// ```rust
260 /// use languages_rs::Value;
261 ///
262 /// #[cfg(feature = "with-json")]
263 /// fn main() {
264 /// let value = Value::from_string(String::from("[\"1\", \"2\"]"));
265 /// assert!(value.is_ok());
266 /// assert_eq!(
267 /// value.unwrap().get_array(),
268 /// Some(vec![Value::String(String::from("1")), Value::String(String::from("2"))]),
269 /// );
270 /// }
271 ///
272 /// #[cfg(feature = "with-toml")]
273 /// fn main() {
274 /// let value = Value::from_string(String::from("numbers = [\"1\", \"2\"]"));
275 /// assert!(value.is_ok());
276 ///
277 /// let table = value.unwrap().get_object();
278 /// assert!(table.is_some());
279 ///
280 /// let table = table.unwrap();
281 /// let values = table.get("numbers");
282 /// assert!(values.is_some());
283 /// assert_eq!(
284 /// values.unwrap().get_array(),
285 /// Some(vec![Value::String(String::from("1")), Value::String(String::from("2"))]),
286 /// );
287 /// }
288 ///
289 /// #[cfg(all(not(feature = "with-json"), not(feature = "with-toml")))]
290 /// fn main() {}
291 /// ```
292 pub fn get_array(&self) -> Option<Vec<Value>> {
293 match self {
294 Self::Array(data) => Some(data.clone()),
295 _ => None,
296 }
297 }
298
299 /// Check if the current value is an object.
300 ///
301 /// # Example JSON
302 /// ```rust
303 /// use languages_rs::Value;
304 ///
305 /// #[cfg(feature = "with-json")]
306 /// fn main() {
307 /// let value = Value::from_string(String::from("{\"home\":{\"title\":\"Home page\"}}"));
308 /// assert!(value.is_ok());
309 /// assert!(value.unwrap().is_object());
310 /// }
311 ///
312 /// #[cfg(feature = "with-toml")]
313 /// fn main() {
314 /// let value = Value::from_string(String::from("[home]\r\ntitle = \"Home page\""));
315 /// assert!(value.is_ok());
316 /// assert!(value.unwrap().is_object());
317 /// }
318 ///
319 /// #[cfg(all(not(feature = "with-json"), not(feature = "with-toml")))]
320 /// fn main() {}
321 /// ```
322 pub fn is_object(&self) -> bool {
323 matches!(self, Self::Object(_))
324 }
325
326 /// Get the object value.
327 ///
328 /// # Example JSON
329 /// ```rust
330 /// use std::collections::HashMap;
331 ///
332 /// use languages_rs::Value;
333 ///
334 /// #[cfg(feature = "with-json")]
335 /// fn main() {
336 /// let value = Value::from_string(String::from("{ \"title\": \"Home page\" }"));
337 /// assert!(value.is_ok());
338 ///
339 /// let mut data: HashMap<String, Value> = HashMap::new();
340 /// data.insert(String::from("title"), Value::String(String::from("Home page")));
341 ///
342 /// assert_eq!(value.unwrap().get_object(), Some(data));
343 /// }
344 ///
345 /// #[cfg(feature = "with-toml")]
346 /// fn main() {
347 /// let value = Value::from_string(String::from("title = \"Home page\""));
348 /// assert!(value.is_ok());
349 ///
350 /// let mut data: HashMap<String, Value> = HashMap::new();
351 /// data.insert(String::from("title"), Value::String(String::from("Home page")));
352 ///
353 /// assert_eq!(value.unwrap().get_object(), Some(data));
354 /// }
355 ///
356 /// #[cfg(all(not(feature = "with-json"), not(feature = "with-toml")))]
357 /// fn main() {}
358 /// ```
359 pub fn get_object(&self) -> Option<HashMap<String, Value>> {
360 match self {
361 Self::Object(data) => Some(data.clone()),
362 _ => None,
363 }
364 }
365}
366
367impl fmt::Display for Value {
368 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
369 match self {
370 Self::String(value) => write!(f, "{}", value),
371 Self::Array(value) => write!(
372 f,
373 "[{}]",
374 value
375 .iter()
376 .map(|e| format!("{}", e))
377 .collect::<Vec<String>>()
378 .join(", ")
379 ),
380 Self::Object(value) => {
381 write!(
382 f,
383 "{{ {} }}",
384 value
385 .iter()
386 .map(|(key, value)| format!("{}: {}", key, value))
387 .collect::<Vec<String>>()
388 .join(", ")
389 )
390 }
391 }
392 }
393}