hocon_linked/
value.rs

1use std::ops::Index;
2use linked_hash_map::LinkedHashMap;
3
4/// An HOCON document
5///
6/// Values can be retrieved as a basic type, with basic cast between some of the value types:
7/// [Automatic type conversions](https://github.com/lightbend/config/blob/master/HOCON.md#automatic-type-conversions).
8/// If the value if not of the expected type, a `None` will be returned.
9///
10/// If the value is a [`Hocon::Hash`](enum.Hocon.html#variant.Hash), its values can be
11/// accessed by indexing with a `str`, as in `hash[key]`. If the value is an
12/// [`Hocon::Array`](enum.Hocon.html#variant.Array), its values can be accessed by indexing
13/// with a `usize`. An [`Hocon::Hash`](enum.Hocon.html#variant.Hash) whose keys can be
14/// converted to numeric values (`"0"`, `"1"`, ...) can be indexed with a `usize` following
15/// the rules described in
16/// [Conversion of numerically-indexed objects to arrays](https://github.com/lightbend/config/blob/master/HOCON.md#conversion-of-numerically-indexed-objects-to-arrays).
17///
18/// Indexing a `Hocon` value with a wrong key type, or a type of value that can't be indexed
19/// will return a [`Hocon::BadValue`](enum.Hocon.html#variant.BadValue) with an error of type
20/// [`crate::Error::InvalidKey`](enum.Error.html#variant.InvalidKey).
21///
22/// Indexing a `Hocon` value with a key that is not present will return a
23/// [`Hocon::BadValue`](enum.Hocon.html#variant.BadValue) with an error of type
24/// [`crate::Error::MissingKey`](enum.Error.html#variant.MissingKey).
25///
26/// Values can also be accessed as a `Duration` or a size following the rules described in
27/// [Units format](https://github.com/lightbend/config/blob/master/HOCON.md#units-format).
28///
29/// # Usage
30///
31/// ```rust
32/// # use hocon::{HoconLoader, Error, Hocon};
33/// # fn main() -> Result<(), Error> {
34/// // Accessing a value of the expected type
35/// assert_eq!(
36///     HoconLoader::new().load_str(r#"{ a: 7 }"#)?.hocon()?["a"].as_i64(),
37///     Some(7)
38/// );
39///
40/// // Accessing a value with automatic conversion
41/// assert_eq!(
42///     HoconLoader::new().load_str(r#"{ a: off }"#)?.hocon()?["a"].as_bool(),
43///     Some(false)
44/// );
45///
46/// // Accessing an Array
47/// assert_eq!(
48///     HoconLoader::new().load_str(r#"{ a: [ first, second ] }"#)?.hocon()?["a"][0].as_string(),
49///     Some(String::from("first"))
50/// );
51///
52/// // Accessing an Hash with a missing key
53/// assert_eq!(
54///     HoconLoader::new().load_str(r#"{ a: 7 }"#)?.hocon()?["b"],
55///     Hocon::BadValue(Error::MissingKey)
56/// );
57///
58/// // Accessing an Hash as if it was an Array
59/// assert_eq!(
60///     HoconLoader::new().load_str(r#"{ a: 7 }"#)?.hocon()?[0],
61///     Hocon::BadValue(Error::InvalidKey)
62/// );
63/// # Ok(())
64/// # }
65/// ```
66#[derive(Debug, Clone, PartialEq)]
67pub enum Hocon {
68    /// A floating value
69    Real(f64),
70    /// An integer value
71    Integer(i64),
72    /// A string
73    String(String),
74    /// A boolean
75    Boolean(bool),
76    /// An array of `Hocon` values
77    Array(Vec<Hocon>),
78    /// An HashMap of `Hocon` values with keys
79    Hash(LinkedHashMap<String, Hocon>),
80    /// A null value
81    Null,
82    /// A `BadValue`, marking an error in parsing or a missing value
83    BadValue(crate::Error),
84}
85
86static NOT_FOUND: Hocon = Hocon::BadValue(crate::Error::MissingKey);
87static INVALID_KEY: Hocon = Hocon::BadValue(crate::Error::InvalidKey);
88
89impl<'a> Index<&'a str> for Hocon {
90    type Output = Hocon;
91
92    fn index(&self, idx: &'a str) -> &Self::Output {
93        match self {
94            Hocon::Hash(hash) => hash.get(idx).unwrap_or(&NOT_FOUND),
95            _ => &INVALID_KEY,
96        }
97    }
98}
99impl Index<usize> for Hocon {
100    type Output = Hocon;
101
102    fn index(&self, idx: usize) -> &Self::Output {
103        match self {
104            Hocon::Array(vec) => vec.get(idx).unwrap_or(&NOT_FOUND),
105            Hocon::Hash(hash) => {
106                let mut keys_as_usize = hash
107                    .keys()
108                    .filter_map(|k| k.parse::<usize>().ok().map(|v| (k, v)))
109                    .collect::<Vec<_>>();
110                keys_as_usize.sort_by(|(_, v0), (_, v1)| v0.cmp(v1));
111                keys_as_usize
112                    .get(idx)
113                    .and_then(|(k, _)| hash.get(*k))
114                    .unwrap_or(&INVALID_KEY)
115            }
116            _ => &INVALID_KEY,
117        }
118    }
119}
120
121impl Hocon {
122    /// Try to cast a value as a `f64` value
123    pub fn as_f64(&self) -> Option<f64> {
124        match *self {
125            Hocon::Real(ref v) => Some(*v),
126            Hocon::Integer(ref v) => Some(*v as f64),
127            Hocon::String(ref v) => v.parse::<f64>().ok(),
128            _ => None,
129        }
130    }
131
132    /// Try to cast a value as a `i64` value
133    pub fn as_i64(&self) -> Option<i64> {
134        match *self {
135            Hocon::Integer(ref v) => Some(*v),
136            Hocon::String(ref v) => v.parse::<i64>().ok(),
137            _ => None,
138        }
139    }
140
141    /// Try to cast a value as a `String` value
142    pub fn as_string(&self) -> Option<String> {
143        match *self {
144            Hocon::String(ref v) => Some(v.to_string()),
145            Hocon::Boolean(true) => Some("true".to_string()),
146            Hocon::Boolean(false) => Some("false".to_string()),
147            Hocon::Integer(i) => Some(i.to_string()),
148            Hocon::Real(f) => Some(f.to_string()),
149            _ => None,
150        }
151    }
152
153    pub(crate) fn as_internal_string(&self) -> Option<String> {
154        match *self {
155            Hocon::String(ref v) => Some(v.to_string()),
156            Hocon::Boolean(true) => Some("true".to_string()),
157            Hocon::Boolean(false) => Some("false".to_string()),
158            Hocon::Integer(i) => Some(i.to_string()),
159            Hocon::Real(f) => Some(f.to_string()),
160            Hocon::Null => Some("null".to_string()),
161            _ => None,
162        }
163    }
164
165    /// Try to cast a value as a `bool` value
166    pub fn as_bool(&self) -> Option<bool> {
167        match *self {
168            Hocon::Boolean(ref v) => Some(*v),
169            Hocon::String(ref v) if v == "yes" || v == "true" || v == "on" => Some(true),
170            Hocon::String(ref v) if v == "no" || v == "false" || v == "off" => Some(false),
171            _ => None,
172        }
173    }
174}
175
176mod unit_format {
177    use nom::*;
178
179    named!(
180        parse_float<types::CompleteStr, f64>,
181        complete!(flat_map!(recognize_float, parse_to!(f64)))
182    );
183
184    pub(crate) fn value_and_unit(s: &str) -> Option<(f64, &str)> {
185        match parse_float(types::CompleteStr(s)) {
186            Ok((remaining, float)) => Some((float, &remaining)),
187            _ => None,
188        }
189    }
190}
191
192macro_rules! units {
193    ( match $input:expr, $( $first_unit:expr, $( $unit:expr ),* => $scale:expr ),* ) => {
194        match $input {
195            $(
196                Some((value, $first_unit))
197                $(
198                    | Some((value, $unit))
199                )* => Some(value * $scale),
200            )*
201            _ => None
202        }
203    };
204}
205
206impl Hocon {
207    /// Try to return a value as a size in bytes according to
208    /// [size in bytes format](https://github.com/lightbend/config/blob/master/HOCON.md#size-in-bytes-format).
209    ///
210    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
211    /// plus an optional unit string.
212    ///
213    /// # Example
214    ///
215    /// ```rust
216    /// # use hocon::{Hocon, HoconLoader, Error};
217    /// # fn main() -> Result<(), Error> {
218    /// assert_eq!(
219    ///     HoconLoader::new().load_str(r#"{ size = 1.5KiB }"#)?.hocon()?["size"].as_bytes(),
220    ///     Some(1536.0)
221    /// );
222    /// # Ok(())
223    /// # }
224    /// ```
225    pub fn as_bytes(&self) -> Option<f64> {
226        match *self {
227            Hocon::Integer(ref i) => Some(*i as f64),
228            Hocon::Real(ref f) => Some(*f),
229            Hocon::String(ref s) => units!(
230                match unit_format::value_and_unit(s).map(|(value, unit)| (value, unit.trim())),
231                 "", "B", "b", "byte", "bytes"                     => 1.0,
232                 "kB", "kilobyte", "kilobytes"                     => 10.0f64.powf(3.0),
233                 "MB", "megabyte", "megabytes"                     => 10.0f64.powf(6.0),
234                 "GB", "gigabyte", "gigabytes"                     => 10.0f64.powf(9.0),
235                 "TB", "terabyte", "terabytes"                     => 10.0f64.powf(12.0),
236                 "PB", "petabyte", "petabytes"                     => 10.0f64.powf(15.0),
237                 "EB", "exabyte", "exabytes"                       => 10.0f64.powf(18.0),
238                 "ZB", "zettabyte", "zettabytes"                   => 10.0f64.powf(21.0),
239                 "YB", "yottabyte", "yottabytes"                   => 10.0f64.powf(24.0),
240                 "K", "k", "Ki", "KiB", "kibibyte", "kibibytes"    => 2.0f64.powf(10.0),
241                 "M", "m", "Mi", "MiB", "mebibyte", "mebibytes"    => 2.0f64.powf(20.0),
242                 "G", "g", "Gi", "GiB", "gibibyte", "gibibytes"    => 2.0f64.powf(30.0),
243                 "T", "t", "Ti", "TiB", "tebibyte", "tebibytes"    => 2.0f64.powf(40.0),
244                 "P", "p", "Pi", "PiB", "pebibyte", "pebibytes"    => 2.0f64.powf(50.0),
245                 "E", "e", "Ei", "EiB", "exbibyte", "exbibytes"    => 2.0f64.powf(60.0),
246                 "Z", "z", "Zi", "ZiB", "zebibyte", "zebibytes"    => 2.0f64.powf(70.0),
247                 "Y", "y", "Yi", "YiB", "yobibyte", "yobibytes"    => 2.0f64.powf(80.0)
248            ),
249            _ => None,
250        }
251    }
252
253    /// Try to return a value as a duration in milliseconds according to
254    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
255    ///
256    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
257    /// plus an optional unit string.
258    ///
259    /// # Example
260    ///
261    /// ```rust
262    /// # use hocon::{Hocon, HoconLoader, Error};
263    /// # fn main() -> Result<(), Error> {
264    /// assert_eq!(
265    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
266    ///         .hocon()?["duration"].as_milliseconds(),
267    ///     Some(5400000.0)
268    /// );
269    /// # Ok(())
270    /// # }
271    /// ```
272    pub fn as_milliseconds(&self) -> Option<f64> {
273        match *self {
274            Hocon::Integer(ref i) => Some(*i as f64),
275            Hocon::Real(ref f) => Some(*f),
276            Hocon::String(ref s) => Self::str_as_milliseconds(s),
277            _ => None,
278        }
279    }
280
281    pub(crate) fn str_as_milliseconds(s: &str) -> Option<f64> {
282        units!(
283            match unit_format::value_and_unit(s).map(|(value, unit)| (value, unit.trim())),
284            "ns", "nano", "nanos", "nanosecond", "nanoseconds"          => 10.0f64.powf(-6.0),
285            "us", "micro", "micros", "microsecond", "microseconds"      => 10.0f64.powf(-3.0),
286            "", "ms", "milli", "millis", "millisecond", "milliseconds"  => 1.0,
287            "s", "second", "seconds"                                    => 1_000.0,
288            "m", "minute", "minutes"                                    => 1_000.0 * 60.0,
289            "h", "hour", "hours"                                        => 1_000.0 * 60.0 * 60.0,
290            "d", "day", "days"                                          => 1_000.0 * 60.0 * 60.0 * 24.0,
291            "w", "week", "weeks"                                        => 1_000.0 * 60.0 * 60.0 * 24.0 * 7.0,
292            "mo", "month", "months"                                     => 1_000.0 * 60.0 * 60.0 * 24.0 * 30.0,
293            "y", "year", "years"                                        => 1_000.0 * 60.0 * 60.0 * 24.0 * 365.0
294        )
295    }
296
297    /// Try to return a value as a duration in nanoseconds according to
298    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
299    ///
300    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
301    /// plus an optional unit string.
302    ///
303    /// # Example
304    ///
305    /// ```rust
306    /// # use hocon::{Hocon, HoconLoader, Error};
307    /// # fn main() -> Result<(), Error> {
308    /// assert_eq!(
309    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
310    ///         .hocon()?["duration"].as_nanoseconds(),
311    ///     Some(5400000000000.0)
312    /// );
313    /// # Ok(())
314    /// # }
315    /// ```
316    pub fn as_nanoseconds(&self) -> Option<f64> {
317        self.as_milliseconds().map(|v| v * 10.0f64.powf(6.0))
318    }
319
320    /// Try to return a value as a duration in microseconds according to
321    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
322    ///
323    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
324    /// plus an optional unit string.
325    ///
326    /// # Example
327    ///
328    /// ```rust
329    /// # use hocon::{Hocon, HoconLoader, Error};
330    /// # fn main() -> Result<(), Error> {
331    /// assert_eq!(
332    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
333    ///         .hocon()?["duration"].as_microseconds(),
334    ///     Some(5400000000.0)
335    /// );
336    /// # Ok(())
337    /// # }
338    /// ```
339    pub fn as_microseconds(&self) -> Option<f64> {
340        self.as_milliseconds().map(|v| v * 10.0f64.powf(3.0))
341    }
342
343    /// Try to return a value as a duration in seconds according to
344    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
345    ///
346    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
347    /// plus an optional unit string.
348    ///
349    /// # Example
350    ///
351    /// ```rust
352    /// # use hocon::{Hocon, HoconLoader, Error};
353    /// # fn main() -> Result<(), Error> {
354    /// assert_eq!(
355    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
356    ///         .hocon()?["duration"].as_seconds(),
357    ///     Some(5400.0)
358    /// );
359    /// # Ok(())
360    /// # }
361    /// ```
362    pub fn as_seconds(&self) -> Option<f64> {
363        self.as_milliseconds().map(|v| v * 10.0f64.powf(-3.0))
364    }
365
366    /// Try to return a value as a duration in minutes according to
367    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
368    ///
369    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
370    /// plus an optional unit string.
371    ///
372    /// # Example
373    ///
374    /// ```rust
375    /// # use hocon::{Hocon, HoconLoader, Error};
376    /// # fn main() -> Result<(), Error> {
377    /// assert_eq!(
378    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
379    ///         .hocon()?["duration"].as_minutes(),
380    ///     Some(90.0)
381    /// );
382    /// # Ok(())
383    /// # }
384    /// ```
385    pub fn as_minutes(&self) -> Option<f64> {
386        self.as_milliseconds()
387            .map(|v| v * 10.0f64.powf(-3.0) / 60.0)
388    }
389
390    /// Try to return a value as a duration in hours according to
391    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
392    ///
393    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
394    /// plus an optional unit string.
395    ///
396    /// # Example
397    ///
398    /// ```rust
399    /// # use hocon::{Hocon, HoconLoader, Error};
400    /// # fn main() -> Result<(), Error> {
401    /// assert_eq!(
402    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
403    ///         .hocon()?["duration"].as_hours(),
404    ///     Some(1.5)
405    /// );
406    /// # Ok(())
407    /// # }
408    /// ```
409    pub fn as_hours(&self) -> Option<f64> {
410        self.as_milliseconds()
411            .map(|v| v * 10.0f64.powf(-3.0) / 60.0 / 60.0)
412    }
413
414    /// Try to return a value as a duration in days according to
415    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
416    ///
417    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
418    /// plus an optional unit string.
419    ///
420    /// # Example
421    ///
422    /// ```rust
423    /// # use hocon::{Hocon, HoconLoader, Error};
424    /// # fn main() -> Result<(), Error> {
425    /// assert_eq!(
426    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
427    ///         .hocon()?["duration"].as_days(),
428    ///     Some(0.0625)
429    /// );
430    /// # Ok(())
431    /// # }
432    /// ```
433    pub fn as_days(&self) -> Option<f64> {
434        self.as_milliseconds()
435            .map(|v| v * 10.0f64.powf(-3.0) / 60.0 / 60.0 / 24.0)
436    }
437
438    /// Try to return a value as a duration in weeks according to
439    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
440    ///
441    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
442    /// plus an optional unit string.
443    ///
444    /// # Example
445    ///
446    /// ```rust
447    /// # use hocon::{Hocon, HoconLoader, Error};
448    /// # fn main() -> Result<(), Error> {
449    /// assert_eq!(
450    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 days  }"#)?
451    ///         .hocon()?["duration"].as_weeks(),
452    ///     Some(0.21428571428571427)
453    /// );
454    /// # Ok(())
455    /// # }
456    /// ```
457    pub fn as_weeks(&self) -> Option<f64> {
458        self.as_milliseconds()
459            .map(|v| v * 10.0f64.powf(-3.0) / 60.0 / 60.0 / 24.0 / 7.0)
460    }
461
462    /// Try to return a value as a duration in months according to
463    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
464    ///
465    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
466    /// plus an optional unit string.
467    ///
468    /// # Example
469    ///
470    /// ```rust
471    /// # use hocon::{Hocon, HoconLoader, Error};
472    /// # fn main() -> Result<(), Error> {
473    /// assert_eq!(
474    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 days  }"#)?
475    ///         .hocon()?["duration"].as_months(),
476    ///     Some(0.05)
477    /// );
478    /// # Ok(())
479    /// # }
480    /// ```
481    pub fn as_months(&self) -> Option<f64> {
482        self.as_milliseconds()
483            .map(|v| v * 10.0f64.powf(-3.0) / 60.0 / 60.0 / 24.0 / 30.0)
484    }
485
486    /// Try to return a value as a duration in years according to
487    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
488    ///
489    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
490    /// plus an optional unit string.
491    ///
492    /// # Example
493    ///
494    /// ```rust
495    /// # use hocon::{Hocon, HoconLoader, Error};
496    /// # fn main() -> Result<(), Error> {
497    /// assert_eq!(
498    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 days  }"#)?
499    ///         .hocon()?["duration"].as_years(),
500    ///     Some(0.00410958904109589)
501    /// );
502    /// # Ok(())
503    /// # }
504    /// ```
505    pub fn as_years(&self) -> Option<f64> {
506        self.as_milliseconds()
507            .map(|v| v * 10.0f64.powf(-3.0) / 60.0 / 60.0 / 24.0 / 365.0)
508    }
509
510    /// Try to return a value as a duration according to
511    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
512    ///
513    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
514    /// plus an optional unit string.
515    ///
516    /// # Example
517    ///
518    /// ```rust
519    /// # use hocon::{Hocon, HoconLoader, Error};
520    /// # fn main() -> Result<(), Error> {
521    /// assert_eq!(
522    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hours  }"#)?
523    ///         .hocon()?["duration"].as_duration(),
524    ///     Some(std::time::Duration::from_secs(5400))
525    /// );
526    /// # Ok(())
527    /// # }
528    /// ```
529    pub fn as_duration(&self) -> Option<std::time::Duration> {
530        self.as_nanoseconds()
531            .map(|v| std::time::Duration::from_nanos(v as u64))
532    }
533}
534
535impl Hocon {
536    /// Deserialize the loaded documents to the target type
537    ///
538    /// # Errors
539    ///
540    /// * [`Error::Deserialization`](enum.Error.html#variant.Deserialization) if there was a
541    /// serde error during deserialization (missing required field, type issue, ...)
542    ///
543    /// # Additional errors in strict mode
544    ///
545    /// * [`Error::Include`](enum.Error.html#variant.Include) if there was an issue with an
546    /// included file
547    /// * [`Error::KeyNotFound`](enum.Error.html#variant.KeyNotFound) if there is a substitution
548    /// with a key that is not present in the document
549    /// * [`Error::DisabledExternalUrl`](enum.Error.html#variant.DisabledExternalUrl) if crate
550    /// was built without feature `url-support` and an `include url("...")` was found
551    #[cfg(feature = "serde-support")]
552    pub fn resolve<'de, T>(self) -> Result<T, crate::Error>
553    where
554        T: ::serde::Deserialize<'de>,
555    {
556        crate::serde::from_hocon(self).map_err(|err| crate::Error::Deserialization {
557            message: err.message,
558        })
559    }
560}
561
562#[cfg(test)]
563mod tests {
564    use super::*;
565
566    #[test]
567    fn access_on_string() {
568        let val = Hocon::String(String::from("test"));
569
570        assert_eq!(val.as_bool(), None);
571        assert_eq!(val.as_f64(), None);
572        assert_eq!(val.as_i64(), None);
573        assert_eq!(val.as_string(), Some(String::from("test")));
574        assert_eq!(val[0], INVALID_KEY);
575        assert_eq!(val["a"], INVALID_KEY);
576    }
577
578    #[test]
579    fn access_on_real() {
580        let val = Hocon::Real(5.6);
581
582        assert_eq!(val.as_bool(), None);
583        assert_eq!(val.as_f64(), Some(5.6));
584        assert_eq!(val.as_i64(), None);
585        assert_eq!(val.as_string(), Some(String::from("5.6")));
586        assert_eq!(val[0], INVALID_KEY);
587        assert_eq!(val["a"], INVALID_KEY);
588    }
589
590    #[test]
591    fn access_on_integer() {
592        let val = Hocon::Integer(5);
593
594        assert_eq!(val.as_bool(), None);
595        assert_eq!(val.as_f64(), Some(5.0));
596        assert_eq!(val.as_i64(), Some(5));
597        assert_eq!(val.as_string(), Some(String::from("5")));
598        assert_eq!(val[0], INVALID_KEY);
599        assert_eq!(val["a"], INVALID_KEY);
600    }
601
602    #[test]
603    fn access_on_boolean_false() {
604        let val = Hocon::Boolean(false);
605
606        assert_eq!(val.as_bool(), Some(false));
607        assert_eq!(val.as_f64(), None);
608        assert_eq!(val.as_i64(), None);
609        assert_eq!(val.as_string(), Some(String::from("false")));
610        assert_eq!(val[0], INVALID_KEY);
611        assert_eq!(val["a"], INVALID_KEY);
612    }
613
614    #[test]
615    fn access_on_boolean_true() {
616        let val = Hocon::Boolean(true);
617
618        assert_eq!(val.as_bool(), Some(true));
619        assert_eq!(val.as_f64(), None);
620        assert_eq!(val.as_i64(), None);
621        assert_eq!(val.as_string(), Some(String::from("true")));
622        assert_eq!(val[0], INVALID_KEY);
623        assert_eq!(val["a"], INVALID_KEY);
624    }
625
626    #[test]
627    fn access_on_null() {
628        let val = Hocon::Null;
629
630        assert_eq!(val.as_bool(), None);
631        assert_eq!(val.as_f64(), None);
632        assert_eq!(val.as_i64(), None);
633        assert_eq!(val.as_string(), None);
634        assert_eq!(val[0], INVALID_KEY);
635        assert_eq!(val["a"], INVALID_KEY);
636    }
637
638    #[test]
639    fn access_on_bad_value() {
640        let val = Hocon::BadValue(crate::Error::DisabledExternalUrl);
641
642        assert_eq!(val.as_bool(), None);
643        assert_eq!(val.as_f64(), None);
644        assert_eq!(val.as_i64(), None);
645        assert_eq!(val.as_string(), None);
646        assert_eq!(val[0], INVALID_KEY);
647        assert_eq!(val["a"], INVALID_KEY);
648    }
649
650    #[test]
651    fn access_on_array() {
652        let val = Hocon::Array(vec![Hocon::Integer(5), Hocon::Integer(6)]);
653
654        assert_eq!(val.as_bool(), None);
655        assert_eq!(val.as_f64(), None);
656        assert_eq!(val.as_i64(), None);
657        assert_eq!(val.as_string(), None);
658        assert_eq!(val[0], Hocon::Integer(5));
659        assert_eq!(val[1], Hocon::Integer(6));
660        assert_eq!(val[2], NOT_FOUND);
661        assert_eq!(val["a"], INVALID_KEY);
662    }
663
664    #[test]
665    fn access_on_hash() {
666        let mut hm = LinkedHashMap::new();
667        hm.insert(String::from("a"), Hocon::Integer(5));
668        hm.insert(String::from("b"), Hocon::Integer(6));
669        let val = Hocon::Hash(hm);
670
671        assert_eq!(val.as_bool(), None);
672        assert_eq!(val.as_f64(), None);
673        assert_eq!(val.as_i64(), None);
674        assert_eq!(val.as_string(), None);
675        assert_eq!(val[0], INVALID_KEY);
676        assert_eq!(val["a"], Hocon::Integer(5));
677        assert_eq!(val["b"], Hocon::Integer(6));
678        assert_eq!(val["c"], NOT_FOUND);
679    }
680
681    #[test]
682    fn cast_string() {
683        assert_eq!(Hocon::String(String::from("true")).as_bool(), Some(true));
684        assert_eq!(Hocon::String(String::from("yes")).as_bool(), Some(true));
685        assert_eq!(Hocon::String(String::from("on")).as_bool(), Some(true));
686        assert_eq!(Hocon::String(String::from("false")).as_bool(), Some(false));
687        assert_eq!(Hocon::String(String::from("no")).as_bool(), Some(false));
688        assert_eq!(Hocon::String(String::from("off")).as_bool(), Some(false));
689
690        assert_eq!(Hocon::String(String::from("5.6")).as_f64(), Some(5.6));
691        assert_eq!(Hocon::String(String::from("5.6")).as_i64(), None);
692        assert_eq!(Hocon::String(String::from("5")).as_f64(), Some(5.0));
693        assert_eq!(Hocon::String(String::from("5")).as_i64(), Some(5));
694    }
695
696    #[test]
697    fn access_hash_as_array() {
698        let mut hm = LinkedHashMap::new();
699        hm.insert(String::from("0"), Hocon::Integer(5));
700        hm.insert(String::from("a"), Hocon::Integer(6));
701        hm.insert(String::from("2"), Hocon::Integer(7));
702        let val = Hocon::Hash(hm);
703
704        assert_eq!(val.as_bool(), None);
705        assert_eq!(val.as_f64(), None);
706        assert_eq!(val.as_i64(), None);
707        assert_eq!(val.as_string(), None);
708        assert_eq!(val[0], Hocon::Integer(5));
709        assert_eq!(val[1], Hocon::Integer(7));
710        assert_eq!(val[2], INVALID_KEY);
711        assert_eq!(val["0"], Hocon::Integer(5));
712        assert_eq!(val["a"], Hocon::Integer(6));
713        assert_eq!(val["2"], Hocon::Integer(7));
714    }
715
716    #[test]
717    fn access_on_bytes() {
718        let val = Hocon::Array(vec![
719            Hocon::Integer(5),
720            Hocon::Real(6.5),
721            Hocon::String(String::from("7")),
722            Hocon::String(String::from("8kB")),
723            Hocon::String(String::from("9 EB")),
724            Hocon::String(String::from("10.5MiB")),
725            Hocon::String(String::from("5unit")),
726            Hocon::Boolean(false),
727        ]);
728
729        assert_eq!(val[0].as_bytes(), Some(5.0));
730        assert_eq!(val[1].as_bytes(), Some(6.5));
731        assert_eq!(val[2].as_bytes(), Some(7.0));
732        assert_eq!(val[3].as_bytes(), Some(8.0 * 1_000.0));
733        assert_eq!(val[4].as_bytes(), Some(9.0 * 10.0f64.powf(18.0)));
734        assert_eq!(val[5].as_bytes(), Some(10.5 * 2.0f64.powf(20.0)));
735        assert_eq!(val[6].as_bytes(), None);
736        assert_eq!(val[7].as_bytes(), None);
737    }
738
739    #[test]
740    fn access_on_bytes_all_bytes_units() {
741        for unit in vec!["B", "b", "byte", "bytes"] {
742            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
743            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0));
744        }
745
746        for unit in vec!["kB", "kilobyte", "kilobytes"] {
747            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
748            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 10.0f64.powf(3.0)));
749        }
750        for unit in vec!["MB", "megabyte", "megabytes"] {
751            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
752            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 10.0f64.powf(6.0)));
753        }
754        for unit in vec!["GB", "gigabyte", "gigabytes"] {
755            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
756            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 10.0f64.powf(9.0)));
757        }
758        for unit in vec!["TB", "terabyte", "terabytes"] {
759            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
760            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 10.0f64.powf(12.0)));
761        }
762        for unit in vec!["PB", "petabyte", "petabytes"] {
763            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
764            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 10.0f64.powf(15.0)));
765        }
766        for unit in vec!["EB", "exabyte", "exabytes"] {
767            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
768            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 10.0f64.powf(18.0)));
769        }
770        for unit in vec!["ZB", "zettabyte", "zettabytes"] {
771            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
772            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 10.0f64.powf(21.0)));
773        }
774        for unit in vec!["YB", "yottabyte", "yottabytes"] {
775            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
776            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 10.0f64.powf(24.0)));
777        }
778
779        for unit in vec!["K", "k", "Ki", "KiB", "kibibyte", "kibibytes"] {
780            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
781            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 2.0f64.powf(10.0)));
782        }
783        for unit in vec!["M", "m", "Mi", "MiB", "mebibyte", "mebibytes"] {
784            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
785            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 2.0f64.powf(20.0)));
786        }
787        for unit in vec!["G", "g", "Gi", "GiB", "gibibyte", "gibibytes"] {
788            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
789            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 2.0f64.powf(30.0)));
790        }
791        for unit in vec!["T", "t", "Ti", "TiB", "tebibyte", "tebibytes"] {
792            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
793            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 2.0f64.powf(40.0)));
794        }
795        for unit in vec!["P", "p", "Pi", "PiB", "pebibyte", "pebibytes"] {
796            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
797            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 2.0f64.powf(50.0)));
798        }
799        for unit in vec!["E", "e", "Ei", "EiB", "exbibyte", "exbibytes"] {
800            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
801            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 2.0f64.powf(60.0)));
802        }
803        for unit in vec!["Z", "z", "Zi", "ZiB", "zebibyte", "zebibytes"] {
804            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
805            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 2.0f64.powf(70.0)));
806        }
807        for unit in vec!["Y", "y", "Yi", "YiB", "yobibyte", "yobibytes"] {
808            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
809            assert_eq!(dbg!(val)[0].as_bytes(), Some(8.0 * 2.0f64.powf(80.0)));
810        }
811    }
812
813    #[test]
814    fn access_on_duration() {
815        let mut hm = LinkedHashMap::new();
816        hm.insert(String::from("ns"), Hocon::String(String::from("1ns")));
817        hm.insert(String::from("us"), Hocon::String(String::from("1us")));
818        hm.insert(String::from("ms"), Hocon::String(String::from("1ms")));
819        hm.insert(String::from("s"), Hocon::String(String::from("1s")));
820        hm.insert(String::from("m"), Hocon::String(String::from("1m")));
821        hm.insert(String::from("h"), Hocon::String(String::from("1h")));
822        hm.insert(String::from("d"), Hocon::String(String::from("1d")));
823        hm.insert(String::from("w"), Hocon::String(String::from("1w")));
824        hm.insert(String::from("mo"), Hocon::String(String::from("1mo")));
825        hm.insert(String::from("y"), Hocon::String(String::from("1y")));
826        let val = Hocon::Hash(hm);
827
828        assert_eq!(val["ns"].as_nanoseconds(), Some(1.0));
829        assert_eq!(
830            val["ns"].as_duration(),
831            Some(std::time::Duration::from_nanos(1))
832        );
833        assert_eq!(val["us"].as_microseconds(), Some(1.0));
834        assert_eq!(
835            val["us"].as_duration(),
836            Some(std::time::Duration::from_micros(1))
837        );
838        assert_eq!(val["ms"].as_milliseconds(), Some(1.0));
839        assert_eq!(
840            val["ms"].as_duration(),
841            Some(std::time::Duration::from_millis(1))
842        );
843        assert_eq!(val["s"].as_seconds(), Some(1.0));
844        assert_eq!(
845            val["s"].as_duration(),
846            Some(std::time::Duration::from_secs(1))
847        );
848        assert_eq!(val["m"].as_minutes(), Some(1.0));
849        assert_eq!(
850            val["m"].as_duration(),
851            Some(std::time::Duration::from_secs(60))
852        );
853        assert_eq!(val["h"].as_hours(), Some(1.0));
854        assert_eq!(
855            val["h"].as_duration(),
856            Some(std::time::Duration::from_secs(60 * 60))
857        );
858        assert_eq!(val["d"].as_days(), Some(1.0));
859        assert_eq!(
860            val["d"].as_duration(),
861            Some(std::time::Duration::from_secs(60 * 60 * 24))
862        );
863        assert_eq!(val["w"].as_weeks(), Some(1.0));
864        assert_eq!(
865            val["w"].as_duration(),
866            Some(std::time::Duration::from_secs(60 * 60 * 24 * 7))
867        );
868        assert_eq!(val["mo"].as_months(), Some(1.0));
869        assert_eq!(
870            val["mo"].as_duration(),
871            Some(std::time::Duration::from_secs(60 * 60 * 24 * 30))
872        );
873        assert_eq!(val["y"].as_years(), Some(1.0));
874        assert_eq!(
875            val["y"].as_duration(),
876            Some(std::time::Duration::from_secs(60 * 60 * 24 * 365))
877        );
878    }
879}