Skip to main content

hocon/
value.rs

1use linked_hash_map::LinkedHashMap;
2use std::ops::Index;
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_key(|(_, v0)| *v0);
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::IResult;
178    use nom::Parser;
179    use nom::character::complete::digit1;
180    use nom::combinator::opt;
181    use nom::combinator::recognize;
182    use nom::sequence::pair;
183
184    fn parse_float(input: &str) -> IResult<&str, Option<f64>> {
185        // Parse a number: [-]digits[.digits]
186        // We don't use recognize_float because it accepts scientific notation like "8E10"
187        // which conflicts with units like "8EB" (8 exabytes)
188        let (remaining, parsed) = recognize((
189            opt(nom::character::complete::char('-')),
190            digit1,
191            opt(pair(nom::character::complete::char('.'), digit1)),
192        ))
193        .parse(input)?;
194        let parsed: Option<f64> = parsed.parse().ok();
195        Ok((remaining, parsed))
196    }
197
198    pub(crate) fn value_and_unit(s: &str) -> Option<(f64, &str)> {
199        match parse_float(s) {
200            Ok((remaining, Some(float))) => Some((float, remaining)),
201            _ => None,
202        }
203    }
204}
205
206macro_rules! units {
207    ( match $input:expr, $( $first_unit:expr, $( $unit:expr ),* => $scale:expr ),* ) => {
208        match $input {
209            $(
210                Some((value, $first_unit))
211                $(
212                    | Some((value, $unit))
213                )* => Some((value * $scale as f64) as u64),
214            )*
215            _ => None
216        }
217    };
218}
219
220macro_rules! units_f {
221    ( match $input:expr, $( $first_unit:expr, $( $unit:expr ),* => $scale:expr ),* ) => {
222        match $input {
223            $(
224                Some((value, $first_unit))
225                $(
226                    | Some((value, $unit))
227                )* => Some(value * $scale ),
228            )*
229            _ => None
230        }
231    };
232}
233
234impl Hocon {
235    /// Try to return a value as a size in bytes according to
236    /// [size in bytes format](https://github.com/lightbend/config/blob/master/HOCON.md#size-in-bytes-format).
237    ///
238    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
239    /// plus an optional unit string.
240    ///
241    /// # Example
242    ///
243    /// ```rust
244    /// # use hocon::{Hocon, HoconLoader, Error};
245    /// # fn main() -> Result<(), Error> {
246    /// assert_eq!(
247    ///     HoconLoader::new().load_str(r#"{ size = 1.5KiB }"#)?.hocon()?["size"].as_bytes(),
248    ///     Some(1536)
249    /// );
250    /// # Ok(())
251    /// # }
252    /// ```
253    pub fn as_bytes(&self) -> Option<u64> {
254        match *self {
255            Hocon::Integer(ref i) => Some(*i as u64),
256            // Hocon::Real(ref f) => Some(*f),
257            Hocon::String(ref s) => units!(
258                match unit_format::value_and_unit(s).map(|(value, unit)| (value, unit.trim())),
259                 "", "B", "b", "byte", "bytes"                     => 1,
260                 "kB", "kilobyte", "kilobytes"                     => 10u64.pow(3),
261                 "MB", "megabyte", "megabytes"                     => 10u64.pow(6),
262                 "GB", "gigabyte", "gigabytes"                     => 10u64.pow(9),
263                 "TB", "terabyte", "terabytes"                     => 10u64.pow(12),
264                 "PB", "petabyte", "petabytes"                     => 10u64.pow(15),
265                 "EB", "exabyte", "exabytes"                       => 10u64.pow(18),
266                 "ZB", "zettabyte", "zettabytes"                   => 10u64.pow(21),
267                 "YB", "yottabyte", "yottabytes"                   => 10u64.pow(24),
268                 "K", "k", "Ki", "KiB", "kibibyte", "kibibytes"    => 2u64.pow(10),
269                 "M", "m", "Mi", "MiB", "mebibyte", "mebibytes"    => 2u64.pow(20),
270                 "G", "g", "Gi", "GiB", "gibibyte", "gibibytes"    => 2u64.pow(30),
271                 "T", "t", "Ti", "TiB", "tebibyte", "tebibytes"    => 2u64.pow(40),
272                 "P", "p", "Pi", "PiB", "pebibyte", "pebibytes"    => 2u64.pow(50),
273                 "E", "e", "Ei", "EiB", "exbibyte", "exbibytes"    => 2u64.pow(60),
274                 "Z", "z", "Zi", "ZiB", "zebibyte", "zebibytes"    => 2u64.pow(70),
275                 "Y", "y", "Yi", "YiB", "yobibyte", "yobibytes"    => 2u64.pow(80)
276            ),
277            _ => None,
278        }
279    }
280
281    /// Try to return a value as a duration in milliseconds according to
282    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
283    ///
284    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
285    /// plus an optional unit string.
286    ///
287    /// # Example
288    ///
289    /// ```rust
290    /// # use hocon::{Hocon, HoconLoader, Error};
291    /// # fn main() -> Result<(), Error> {
292    /// assert_eq!(
293    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
294    ///         .hocon()?["duration"].as_milliseconds(),
295    ///     Some(5400000.0)
296    /// );
297    /// # Ok(())
298    /// # }
299    /// ```
300    pub fn as_milliseconds(&self) -> Option<f64> {
301        match *self {
302            Hocon::Integer(ref i) => Some(*i as f64),
303            Hocon::Real(ref f) => Some(*f),
304            Hocon::String(ref s) => Self::str_as_milliseconds(s),
305            _ => None,
306        }
307    }
308
309    pub(crate) fn str_as_milliseconds(s: &str) -> Option<f64> {
310        units_f!(
311            match unit_format::value_and_unit(s).map(|(value, unit)| (value, unit.trim())),
312            "ns", "nano", "nanos", "nanosecond", "nanoseconds"          => 10.0f64.powf(-6.0),
313            "us", "micro", "micros", "microsecond", "microseconds"      => 10.0f64.powf(-3.0),
314            "", "ms", "milli", "millis", "millisecond", "milliseconds"  => 1.0,
315            "s", "second", "seconds"                                    => 1_000.0,
316            "m", "minute", "minutes"                                    => 1_000.0 * 60.0,
317            "h", "hour", "hours"                                        => 1_000.0 * 60.0 * 60.0,
318            "d", "day", "days"                                          => 1_000.0 * 60.0 * 60.0 * 24.0,
319            "w", "week", "weeks"                                        => 1_000.0 * 60.0 * 60.0 * 24.0 * 7.0,
320            "mo", "month", "months"                                     => 1_000.0 * 60.0 * 60.0 * 24.0 * 30.0,
321            "y", "year", "years"                                        => 1_000.0 * 60.0 * 60.0 * 24.0 * 365.0
322        )
323    }
324
325    /// Try to return a value as a duration in nanoseconds according to
326    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
327    ///
328    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
329    /// plus an optional unit string.
330    ///
331    /// # Example
332    ///
333    /// ```rust
334    /// # use hocon::{Hocon, HoconLoader, Error};
335    /// # fn main() -> Result<(), Error> {
336    /// assert_eq!(
337    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
338    ///         .hocon()?["duration"].as_nanoseconds(),
339    ///     Some(5400000000000.0)
340    /// );
341    /// # Ok(())
342    /// # }
343    /// ```
344    pub fn as_nanoseconds(&self) -> Option<f64> {
345        self.as_milliseconds().map(|v| v * 10.0f64.powf(6.0))
346    }
347
348    /// Try to return a value as a duration in microseconds according to
349    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
350    ///
351    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
352    /// plus an optional unit string.
353    ///
354    /// # Example
355    ///
356    /// ```rust
357    /// # use hocon::{Hocon, HoconLoader, Error};
358    /// # fn main() -> Result<(), Error> {
359    /// assert_eq!(
360    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
361    ///         .hocon()?["duration"].as_microseconds(),
362    ///     Some(5400000000.0)
363    /// );
364    /// # Ok(())
365    /// # }
366    /// ```
367    pub fn as_microseconds(&self) -> Option<f64> {
368        self.as_milliseconds().map(|v| v * 10.0f64.powf(3.0))
369    }
370
371    /// Try to return a value as a duration in seconds according to
372    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
373    ///
374    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
375    /// plus an optional unit string.
376    ///
377    /// # Example
378    ///
379    /// ```rust
380    /// # use hocon::{Hocon, HoconLoader, Error};
381    /// # fn main() -> Result<(), Error> {
382    /// assert_eq!(
383    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
384    ///         .hocon()?["duration"].as_seconds(),
385    ///     Some(5400.0)
386    /// );
387    /// # Ok(())
388    /// # }
389    /// ```
390    pub fn as_seconds(&self) -> Option<f64> {
391        self.as_milliseconds().map(|v| v * 10.0f64.powf(-3.0))
392    }
393
394    /// Try to return a value as a duration in minutes according to
395    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
396    ///
397    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
398    /// plus an optional unit string.
399    ///
400    /// # Example
401    ///
402    /// ```rust
403    /// # use hocon::{Hocon, HoconLoader, Error};
404    /// # fn main() -> Result<(), Error> {
405    /// assert_eq!(
406    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
407    ///         .hocon()?["duration"].as_minutes(),
408    ///     Some(90.0)
409    /// );
410    /// # Ok(())
411    /// # }
412    /// ```
413    pub fn as_minutes(&self) -> Option<f64> {
414        self.as_milliseconds()
415            .map(|v| v * 10.0f64.powf(-3.0) / 60.0)
416    }
417
418    /// Try to return a value as a duration in hours according to
419    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
420    ///
421    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
422    /// plus an optional unit string.
423    ///
424    /// # Example
425    ///
426    /// ```rust
427    /// # use hocon::{Hocon, HoconLoader, Error};
428    /// # fn main() -> Result<(), Error> {
429    /// assert_eq!(
430    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
431    ///         .hocon()?["duration"].as_hours(),
432    ///     Some(1.5)
433    /// );
434    /// # Ok(())
435    /// # }
436    /// ```
437    pub fn as_hours(&self) -> Option<f64> {
438        self.as_milliseconds()
439            .map(|v| v * 10.0f64.powf(-3.0) / 60.0 / 60.0)
440    }
441
442    /// Try to return a value as a duration in days according to
443    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
444    ///
445    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
446    /// plus an optional unit string.
447    ///
448    /// # Example
449    ///
450    /// ```rust
451    /// # use hocon::{Hocon, HoconLoader, Error};
452    /// # fn main() -> Result<(), Error> {
453    /// assert_eq!(
454    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hour  }"#)?
455    ///         .hocon()?["duration"].as_days(),
456    ///     Some(0.0625)
457    /// );
458    /// # Ok(())
459    /// # }
460    /// ```
461    pub fn as_days(&self) -> Option<f64> {
462        self.as_milliseconds()
463            .map(|v| v * 10.0f64.powf(-3.0) / 60.0 / 60.0 / 24.0)
464    }
465
466    /// Try to return a value as a duration in weeks according to
467    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
468    ///
469    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
470    /// plus an optional unit string.
471    ///
472    /// # Example
473    ///
474    /// ```rust
475    /// # use hocon::{Hocon, HoconLoader, Error};
476    /// # fn main() -> Result<(), Error> {
477    /// assert_eq!(
478    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 days  }"#)?
479    ///         .hocon()?["duration"].as_weeks(),
480    ///     Some(0.21428571428571427)
481    /// );
482    /// # Ok(())
483    /// # }
484    /// ```
485    pub fn as_weeks(&self) -> Option<f64> {
486        self.as_milliseconds()
487            .map(|v| v * 10.0f64.powf(-3.0) / 60.0 / 60.0 / 24.0 / 7.0)
488    }
489
490    /// Try to return a value as a duration in months according to
491    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
492    ///
493    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
494    /// plus an optional unit string.
495    ///
496    /// # Example
497    ///
498    /// ```rust
499    /// # use hocon::{Hocon, HoconLoader, Error};
500    /// # fn main() -> Result<(), Error> {
501    /// assert_eq!(
502    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 days  }"#)?
503    ///         .hocon()?["duration"].as_months(),
504    ///     Some(0.05)
505    /// );
506    /// # Ok(())
507    /// # }
508    /// ```
509    pub fn as_months(&self) -> Option<f64> {
510        self.as_milliseconds()
511            .map(|v| v * 10.0f64.powf(-3.0) / 60.0 / 60.0 / 24.0 / 30.0)
512    }
513
514    /// Try to return a value as a duration in years according to
515    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
516    ///
517    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
518    /// plus an optional unit string.
519    ///
520    /// # Example
521    ///
522    /// ```rust
523    /// # use hocon::{Hocon, HoconLoader, Error};
524    /// # fn main() -> Result<(), Error> {
525    /// assert_eq!(
526    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 days  }"#)?
527    ///         .hocon()?["duration"].as_years(),
528    ///     Some(0.00410958904109589)
529    /// );
530    /// # Ok(())
531    /// # }
532    /// ```
533    pub fn as_years(&self) -> Option<f64> {
534        self.as_milliseconds()
535            .map(|v| v * 10.0f64.powf(-3.0) / 60.0 / 60.0 / 24.0 / 365.0)
536    }
537
538    /// Try to return a value as a duration according to
539    /// [duration format](https://github.com/lightbend/config/blob/master/HOCON.md#duration-format).
540    ///
541    /// Bare numbers are taken to be in bytes already, while strings are parsed as a number
542    /// plus an optional unit string.
543    ///
544    /// # Example
545    ///
546    /// ```rust
547    /// # use hocon::{Hocon, HoconLoader, Error};
548    /// # fn main() -> Result<(), Error> {
549    /// assert_eq!(
550    ///     HoconLoader::new().load_str(r#"{ duration = 1.5 hours  }"#)?
551    ///         .hocon()?["duration"].as_duration(),
552    ///     Some(std::time::Duration::from_secs(5400))
553    /// );
554    /// # Ok(())
555    /// # }
556    /// ```
557    pub fn as_duration(&self) -> Option<std::time::Duration> {
558        self.as_nanoseconds()
559            .map(|v| std::time::Duration::from_nanos(v as u64))
560    }
561}
562
563impl Hocon {
564    /// Deserialize the loaded documents to the target type
565    ///
566    /// # Errors
567    ///
568    /// * [`Error::Deserialization`](enum.Error.html#variant.Deserialization) if there was a
569    ///   serde error during deserialization (missing required field, type issue, ...)
570    ///
571    /// # Additional errors in strict mode
572    ///
573    /// * [`Error::Include`](enum.Error.html#variant.Include) if there was an issue with an
574    ///   included file
575    /// * [`Error::KeyNotFound`](enum.Error.html#variant.KeyNotFound) if there is a substitution
576    ///   with a key that is not present in the document
577    /// * [`Error::DisabledExternalUrl`](enum.Error.html#variant.DisabledExternalUrl) if crate
578    ///   was built without feature `url-support` and an `include url("...")` was found
579    #[cfg(feature = "serde-support")]
580    pub fn resolve<'de, T>(self) -> Result<T, crate::Error>
581    where
582        T: ::serde::Deserialize<'de>,
583    {
584        crate::serde::from_hocon(self).map_err(|err| crate::Error::Deserialization {
585            message: err.message,
586        })
587    }
588}
589
590#[cfg(test)]
591mod tests {
592    use super::*;
593
594    #[test]
595    fn access_on_string() {
596        let val = Hocon::String(String::from("test"));
597
598        assert_eq!(val.as_bool(), None);
599        assert_eq!(val.as_f64(), None);
600        assert_eq!(val.as_i64(), None);
601        assert_eq!(val.as_string(), Some(String::from("test")));
602        assert_eq!(val[0], INVALID_KEY);
603        assert_eq!(val["a"], INVALID_KEY);
604    }
605
606    #[test]
607    fn access_on_real() {
608        let val = Hocon::Real(5.6);
609
610        assert_eq!(val.as_bool(), None);
611        assert_eq!(val.as_f64(), Some(5.6));
612        assert_eq!(val.as_i64(), None);
613        assert_eq!(val.as_string(), Some(String::from("5.6")));
614        assert_eq!(val[0], INVALID_KEY);
615        assert_eq!(val["a"], INVALID_KEY);
616    }
617
618    #[test]
619    fn access_on_integer() {
620        let val = Hocon::Integer(5);
621
622        assert_eq!(val.as_bool(), None);
623        assert_eq!(val.as_f64(), Some(5.0));
624        assert_eq!(val.as_i64(), Some(5));
625        assert_eq!(val.as_string(), Some(String::from("5")));
626        assert_eq!(val[0], INVALID_KEY);
627        assert_eq!(val["a"], INVALID_KEY);
628    }
629
630    #[test]
631    fn access_on_boolean_false() {
632        let val = Hocon::Boolean(false);
633
634        assert_eq!(val.as_bool(), Some(false));
635        assert_eq!(val.as_f64(), None);
636        assert_eq!(val.as_i64(), None);
637        assert_eq!(val.as_string(), Some(String::from("false")));
638        assert_eq!(val[0], INVALID_KEY);
639        assert_eq!(val["a"], INVALID_KEY);
640    }
641
642    #[test]
643    fn access_on_boolean_true() {
644        let val = Hocon::Boolean(true);
645
646        assert_eq!(val.as_bool(), Some(true));
647        assert_eq!(val.as_f64(), None);
648        assert_eq!(val.as_i64(), None);
649        assert_eq!(val.as_string(), Some(String::from("true")));
650        assert_eq!(val[0], INVALID_KEY);
651        assert_eq!(val["a"], INVALID_KEY);
652    }
653
654    #[test]
655    fn access_on_null() {
656        let val = Hocon::Null;
657
658        assert_eq!(val.as_bool(), None);
659        assert_eq!(val.as_f64(), None);
660        assert_eq!(val.as_i64(), None);
661        assert_eq!(val.as_string(), None);
662        assert_eq!(val[0], INVALID_KEY);
663        assert_eq!(val["a"], INVALID_KEY);
664    }
665
666    #[test]
667    fn access_on_bad_value() {
668        let val = Hocon::BadValue(crate::Error::DisabledExternalUrl);
669
670        assert_eq!(val.as_bool(), None);
671        assert_eq!(val.as_f64(), None);
672        assert_eq!(val.as_i64(), None);
673        assert_eq!(val.as_string(), None);
674        assert_eq!(val[0], INVALID_KEY);
675        assert_eq!(val["a"], INVALID_KEY);
676    }
677
678    #[test]
679    fn access_on_array() {
680        let val = Hocon::Array(vec![Hocon::Integer(5), Hocon::Integer(6)]);
681
682        assert_eq!(val.as_bool(), None);
683        assert_eq!(val.as_f64(), None);
684        assert_eq!(val.as_i64(), None);
685        assert_eq!(val.as_string(), None);
686        assert_eq!(val[0], Hocon::Integer(5));
687        assert_eq!(val[1], Hocon::Integer(6));
688        assert_eq!(val[2], NOT_FOUND);
689        assert_eq!(val["a"], INVALID_KEY);
690    }
691
692    #[test]
693    fn access_on_hash() {
694        let mut hm = LinkedHashMap::new();
695        hm.insert(String::from("a"), Hocon::Integer(5));
696        hm.insert(String::from("b"), Hocon::Integer(6));
697        let val = Hocon::Hash(hm);
698
699        assert_eq!(val.as_bool(), None);
700        assert_eq!(val.as_f64(), None);
701        assert_eq!(val.as_i64(), None);
702        assert_eq!(val.as_string(), None);
703        assert_eq!(val[0], INVALID_KEY);
704        assert_eq!(val["a"], Hocon::Integer(5));
705        assert_eq!(val["b"], Hocon::Integer(6));
706        assert_eq!(val["c"], NOT_FOUND);
707    }
708
709    #[test]
710    fn cast_string() {
711        assert_eq!(Hocon::String(String::from("true")).as_bool(), Some(true));
712        assert_eq!(Hocon::String(String::from("yes")).as_bool(), Some(true));
713        assert_eq!(Hocon::String(String::from("on")).as_bool(), Some(true));
714        assert_eq!(Hocon::String(String::from("false")).as_bool(), Some(false));
715        assert_eq!(Hocon::String(String::from("no")).as_bool(), Some(false));
716        assert_eq!(Hocon::String(String::from("off")).as_bool(), Some(false));
717
718        assert_eq!(Hocon::String(String::from("5.6")).as_f64(), Some(5.6));
719        assert_eq!(Hocon::String(String::from("5.6")).as_i64(), None);
720        assert_eq!(Hocon::String(String::from("5")).as_f64(), Some(5.0));
721        assert_eq!(Hocon::String(String::from("5")).as_i64(), Some(5));
722    }
723
724    #[test]
725    fn access_hash_as_array() {
726        let mut hm = LinkedHashMap::new();
727        hm.insert(String::from("0"), Hocon::Integer(5));
728        hm.insert(String::from("a"), Hocon::Integer(6));
729        hm.insert(String::from("2"), Hocon::Integer(7));
730        let val = Hocon::Hash(hm);
731
732        assert_eq!(val.as_bool(), None);
733        assert_eq!(val.as_f64(), None);
734        assert_eq!(val.as_i64(), None);
735        assert_eq!(val.as_string(), None);
736        assert_eq!(val[0], Hocon::Integer(5));
737        assert_eq!(val[1], Hocon::Integer(7));
738        assert_eq!(val[2], INVALID_KEY);
739        assert_eq!(val["0"], Hocon::Integer(5));
740        assert_eq!(val["a"], Hocon::Integer(6));
741        assert_eq!(val["2"], Hocon::Integer(7));
742    }
743
744    #[test]
745    fn access_on_bytes() {
746        let val = Hocon::Array(vec![
747            Hocon::Integer(5),
748            Hocon::String(String::from("7")),
749            Hocon::String(String::from("8kB")),
750            Hocon::String(String::from("9 EB")),
751            Hocon::String(String::from("10.5MiB")),
752            Hocon::String(String::from("5unit")),
753            Hocon::Boolean(false),
754        ]);
755
756        assert_eq!(val[0].as_bytes(), Some(5));
757        assert_eq!(val[1].as_bytes(), Some(7));
758        assert_eq!(val[2].as_bytes(), Some(8 * 1_000));
759        assert_eq!(val[3].as_bytes(), Some(9 * 10u64.pow(18)));
760        // assert_eq!(val[4].as_bytes(), Some((10.5 * 2.0f64.powf(20.0)) as u64));
761        assert_eq!(val[4].as_bytes(), Some(11010048));
762        assert_eq!(val[5].as_bytes(), None);
763        assert_eq!(val[6].as_bytes(), None);
764    }
765
766    #[test]
767    fn access_on_bytes_all_bytes_units() {
768        for unit in ["B", "b", "byte", "bytes"] {
769            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
770            assert_eq!(dbg!(val)[0].as_bytes(), Some(8));
771        }
772
773        for unit in ["kB", "kilobyte", "kilobytes"] {
774            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
775            assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 10u64.pow(3)));
776        }
777        for unit in ["MB", "megabyte", "megabytes"] {
778            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
779            assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 10u64.pow(6)));
780        }
781        for unit in ["GB", "gigabyte", "gigabytes"] {
782            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
783            assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 10u64.pow(9)));
784        }
785        for unit in ["TB", "terabyte", "terabytes"] {
786            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
787            assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 10u64.pow(12)));
788        }
789        for unit in ["PB", "petabyte", "petabytes"] {
790            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
791            assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 10u64.pow(15)));
792        }
793        for unit in ["EB", "exabyte", "exabytes"] {
794            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
795            assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 10u64.pow(18)));
796        }
797        // Overflow
798        // for unit in ["ZB", "zettabyte", "zettabytes"] {
799        //     let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
800        //     assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 10u64.pow(21)));
801        // }
802        // for unit in ["YB", "yottabyte", "yottabytes"] {
803        //     let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
804        //     assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 10u64.pow(24)));
805        // }
806
807        for unit in ["K", "k", "Ki", "KiB", "kibibyte", "kibibytes"] {
808            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
809            assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 2u64.pow(10)));
810        }
811        for unit in ["M", "m", "Mi", "MiB", "mebibyte", "mebibytes"] {
812            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
813            assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 2u64.pow(20)));
814        }
815        for unit in ["G", "g", "Gi", "GiB", "gibibyte", "gibibytes"] {
816            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
817            assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 2u64.pow(30)));
818        }
819        for unit in ["T", "t", "Ti", "TiB", "tebibyte", "tebibytes"] {
820            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
821            assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 2u64.pow(40)));
822        }
823        for unit in ["P", "p", "Pi", "PiB", "pebibyte", "pebibytes"] {
824            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
825            assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 2u64.pow(50)));
826        }
827        for unit in ["E", "e", "Ei", "EiB", "exbibyte", "exbibytes"] {
828            let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
829            assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 2u64.pow(60)));
830        }
831        // overflow
832        // for unit in ["Z", "z", "Zi", "ZiB", "zebibyte", "zebibytes"] {
833        //     let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
834        //     assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 2u64.pow(70)));
835        // }
836        // for unit in ["Y", "y", "Yi", "YiB", "yobibyte", "yobibytes"] {
837        //     let val = Hocon::Array(vec![Hocon::String(format!("8{}", unit))]);
838        //     assert_eq!(dbg!(val)[0].as_bytes(), Some(8 * 2u64.pow(80)));
839        // }
840    }
841
842    #[test]
843    fn access_on_duration() {
844        let mut hm = LinkedHashMap::new();
845        hm.insert(String::from("ns"), Hocon::String(String::from("1ns")));
846        hm.insert(String::from("us"), Hocon::String(String::from("1us")));
847        hm.insert(String::from("ms"), Hocon::String(String::from("1ms")));
848        hm.insert(String::from("s"), Hocon::String(String::from("1s")));
849        hm.insert(String::from("m"), Hocon::String(String::from("1m")));
850        hm.insert(String::from("h"), Hocon::String(String::from("1h")));
851        hm.insert(String::from("d"), Hocon::String(String::from("1d")));
852        hm.insert(String::from("w"), Hocon::String(String::from("1w")));
853        hm.insert(String::from("mo"), Hocon::String(String::from("1mo")));
854        hm.insert(String::from("y"), Hocon::String(String::from("1y")));
855        let val = Hocon::Hash(hm);
856
857        assert_eq!(val["ns"].as_nanoseconds(), Some(1.0));
858        assert_eq!(
859            val["ns"].as_duration(),
860            Some(std::time::Duration::from_nanos(1))
861        );
862        assert_eq!(val["us"].as_microseconds(), Some(1.0));
863        assert_eq!(
864            val["us"].as_duration(),
865            Some(std::time::Duration::from_micros(1))
866        );
867        assert_eq!(val["ms"].as_milliseconds(), Some(1.0));
868        assert_eq!(
869            val["ms"].as_duration(),
870            Some(std::time::Duration::from_millis(1))
871        );
872        assert_eq!(val["s"].as_seconds(), Some(1.0));
873        assert_eq!(
874            val["s"].as_duration(),
875            Some(std::time::Duration::from_secs(1))
876        );
877        assert_eq!(val["m"].as_minutes(), Some(1.0));
878        assert_eq!(
879            val["m"].as_duration(),
880            Some(std::time::Duration::from_secs(60))
881        );
882        assert_eq!(val["h"].as_hours(), Some(1.0));
883        assert_eq!(
884            val["h"].as_duration(),
885            Some(std::time::Duration::from_secs(60 * 60))
886        );
887        assert_eq!(val["d"].as_days(), Some(1.0));
888        assert_eq!(
889            val["d"].as_duration(),
890            Some(std::time::Duration::from_secs(60 * 60 * 24))
891        );
892        assert_eq!(val["w"].as_weeks(), Some(1.0));
893        assert_eq!(
894            val["w"].as_duration(),
895            Some(std::time::Duration::from_secs(60 * 60 * 24 * 7))
896        );
897        assert_eq!(val["mo"].as_months(), Some(1.0));
898        assert_eq!(
899            val["mo"].as_duration(),
900            Some(std::time::Duration::from_secs(60 * 60 * 24 * 30))
901        );
902        assert_eq!(val["y"].as_years(), Some(1.0));
903        assert_eq!(
904            val["y"].as_duration(),
905            Some(std::time::Duration::from_secs(60 * 60 * 24 * 365))
906        );
907    }
908}