1use linked_hash_map::LinkedHashMap;
2use std::ops::Index;
3
4#[derive(Debug, Clone, PartialEq)]
67pub enum Hocon {
68 Real(f64),
70 Integer(i64),
72 String(String),
74 Boolean(bool),
76 Array(Vec<Hocon>),
78 Hash(LinkedHashMap<String, Hocon>),
80 Null,
82 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 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 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 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 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 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 pub fn as_bytes(&self) -> Option<u64> {
254 match *self {
255 Hocon::Integer(ref i) => Some(*i as u64),
256 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 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 pub fn as_nanoseconds(&self) -> Option<f64> {
345 self.as_milliseconds().map(|v| v * 10.0f64.powf(6.0))
346 }
347
348 pub fn as_microseconds(&self) -> Option<f64> {
368 self.as_milliseconds().map(|v| v * 10.0f64.powf(3.0))
369 }
370
371 pub fn as_seconds(&self) -> Option<f64> {
391 self.as_milliseconds().map(|v| v * 10.0f64.powf(-3.0))
392 }
393
394 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 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 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 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 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 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 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 #[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(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 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 }
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}