boml/
table.rs

1//! See [`TomlTable`].
2
3use {
4	crate::{
5		text::{CowSpan, Text},
6		types::{TomlValue, TomlValueType},
7		TomlError, TomlErrorKind,
8	},
9	std::{
10		collections::{
11			hash_map::{Entry, VacantEntry},
12			HashMap,
13		},
14		ops::Deref,
15	},
16};
17
18/// A set of key/value pairs in TOML.
19#[derive(Debug, PartialEq, Default)]
20pub struct TomlTable<'a> {
21	pub(crate) map: HashMap<CowSpan<'a>, TomlValue<'a>>,
22}
23impl<'a> TomlTable<'a> {
24	/// Gets the value for a key, if that value is a table.
25	pub fn get_table(&self, key: &str) -> Result<&Self, TomlGetError<'_, 'a>> {
26		match self.get(key) {
27			None => Err(TomlGetError::InvalidKey),
28			Some(ref val) => {
29				if let TomlValue::Table(table) = val {
30					Ok(table)
31				} else {
32					Err(TomlGetError::TypeMismatch(val, val.ty()))
33				}
34			}
35		}
36	}
37	/// Gets the value for a key, if that value is a string.
38	pub fn get_string(&self, key: &str) -> Result<&str, TomlGetError<'_, 'a>> {
39		match self.get(key) {
40			None => Err(TomlGetError::InvalidKey),
41			Some(ref val) => match val {
42				TomlValue::String(string) => Ok(string.as_str()),
43				other_val => Err(TomlGetError::TypeMismatch(other_val, other_val.ty())),
44			},
45		}
46	}
47	/// Gets the value for a key, if that value is an integer.
48	pub fn get_integer(&self, key: &str) -> Result<i64, TomlGetError<'_, 'a>> {
49		match self.get(key) {
50			None => Err(TomlGetError::InvalidKey),
51			Some(ref val) => {
52				if let TomlValue::Integer(int) = val {
53					Ok(*int)
54				} else {
55					Err(TomlGetError::TypeMismatch(val, val.ty()))
56				}
57			}
58		}
59	}
60	/// Gets the value for a key, if that value is a float.
61	pub fn get_float(&self, key: &str) -> Result<f64, TomlGetError<'_, 'a>> {
62		match self.get(key) {
63			None => Err(TomlGetError::InvalidKey),
64			Some(ref val) => {
65				if let TomlValue::Float(float) = val {
66					Ok(*float)
67				} else {
68					Err(TomlGetError::TypeMismatch(val, val.ty()))
69				}
70			}
71		}
72	}
73	/// Gets the value for a key, if that value is a boolean.
74	pub fn get_boolean(&self, key: &str) -> Result<bool, TomlGetError<'_, 'a>> {
75		match self.get(key) {
76			None => Err(TomlGetError::InvalidKey),
77			Some(ref val) => {
78				if let TomlValue::Boolean(bool) = val {
79					Ok(*bool)
80				} else {
81					Err(TomlGetError::TypeMismatch(val, val.ty()))
82				}
83			}
84		}
85	}
86	/// Gets the value for a key, if that value is an array.
87	pub fn get_array(&self, key: &str) -> Result<&Vec<TomlValue<'a>>, TomlGetError<'_, 'a>> {
88		match self.get(key) {
89			None => Err(TomlGetError::InvalidKey),
90			Some(ref val) => {
91				if let TomlValue::Array(array, _) = val {
92					Ok(array)
93				} else {
94					Err(TomlGetError::TypeMismatch(val, val.ty()))
95				}
96			}
97		}
98	}
99
100	pub(crate) fn value_entry<'b>(
101		&'b mut self,
102		text: &mut Text<'a>,
103	) -> Result<VacantEntry<'b, CowSpan<'a>, TomlValue<'a>>, TomlError<'a>> {
104		let start = text.idx();
105		let (table, key) = crate::parser::key::parse_nested(text, self)?;
106
107		match table.map.entry(key) {
108			Entry::Occupied(_) => Err(TomlError {
109				src: text.excerpt_to_idx(start..),
110				kind: TomlErrorKind::ReusedKey,
111			}),
112			Entry::Vacant(vacant) => Ok(vacant),
113		}
114	}
115}
116impl<'a> Deref for TomlTable<'a> {
117	type Target = HashMap<CowSpan<'a>, TomlValue<'a>>;
118
119	fn deref(&self) -> &Self::Target {
120		&self.map
121	}
122}
123
124/// Errors for the `get_<type>` methods in [`TomlTable`].
125#[derive(Debug, PartialEq)]
126pub enum TomlGetError<'a, 'table> {
127	/// There was no value for the provided key.
128	InvalidKey,
129	/// The value for the provided key had a different type. Stores the
130	/// value for that key and its type.
131	TypeMismatch(&'a TomlValue<'table>, TomlValueType),
132}
133
134#[cfg(test)]
135mod tests {
136	use super::*;
137
138	struct Tester {
139		key: &'static str,
140		value: TomlValue<'static>,
141	}
142	impl Tester {
143		fn build(self) -> TomlTable<'static> {
144			println!("Running test for key `{}`", self.key);
145
146			let mut table = TomlTable::default();
147			table
148				.value_entry(&mut Text::new(self.key))
149				.unwrap()
150				.insert(self.value);
151			table
152		}
153	}
154
155	#[test]
156	fn test_table_keys() {
157		let basic = Tester {
158			key: "bool",
159			value: TomlValue::Boolean(true),
160		}
161		.build();
162		assert_eq!(basic.get("bool"), Some(&TomlValue::Boolean(true)));
163
164		let dotted = Tester {
165			key: "dot.bool",
166			value: TomlValue::Boolean(true),
167		}
168		.build();
169		let Some(TomlValue::Table(subtable)) = dotted.get("dot") else {
170			panic!()
171		};
172		assert_eq!(subtable.get("bool"), Some(&TomlValue::Boolean(true)));
173
174		let quoted = Tester {
175			key: "'wowza.hi'",
176			value: TomlValue::Boolean(true),
177		}
178		.build();
179		assert_eq!(quoted.get("wowza.hi"), Some(&TomlValue::Boolean(true)));
180
181		let quoted_alt = Tester {
182			key: r#""wowza.hi""#,
183			value: TomlValue::Boolean(true),
184		}
185		.build();
186		assert_eq!(quoted_alt.get("wowza.hi"), Some(&TomlValue::Boolean(true)));
187	}
188}