Skip to main content

boml/
table.rs

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