boml 2.0.0

A dead-simple, efficient, dependency-free TOML parser for Rust.
Documentation
//! See [`TomlTable`].

use {
	crate::{
		TomlError, TomlErrorKind,
		text::{CowSpan, Text},
		types::{TomlArray, TomlValue, TomlValueType},
	},
	std::{
		collections::{
			HashMap,
			hash_map::{Entry, VacantEntry},
		},
		ops::Deref,
	},
};

/// A set of key/value pairs in TOML.
#[derive(Debug, PartialEq, Default)]
pub struct TomlTable<'a> {
	pub(crate) map: HashMap<CowSpan<'a>, TomlValue<'a>>,
	/// Used internally to track if a TOML table was defined multiple times.
	pub(crate) defined: bool,
}
impl<'a> TomlTable<'a> {
	/// Gets the value for a key, if that value is a table.
	pub fn get_table(&self, key: &str) -> Result<&Self, TomlGetError<'_, 'a>> {
		match self.get(key) {
			None => Err(TomlGetError::InvalidKey),
			Some(ref val) => {
				if let TomlValue::Table(table) = val {
					Ok(table)
				} else {
					Err(TomlGetError::TypeMismatch(val, val.ty()))
				}
			}
		}
	}
	/// Gets the value for a key, if that value is a string.
	pub fn get_string(&self, key: &str) -> Result<&str, TomlGetError<'_, 'a>> {
		match self.get(key) {
			None => Err(TomlGetError::InvalidKey),
			Some(ref val) => match val {
				TomlValue::String(string) => Ok(string.as_str()),
				other_val => Err(TomlGetError::TypeMismatch(other_val, other_val.ty())),
			},
		}
	}
	/// Gets the value for a key, if that value is an integer.
	pub fn get_integer(&self, key: &str) -> Result<i64, TomlGetError<'_, 'a>> {
		match self.get(key) {
			None => Err(TomlGetError::InvalidKey),
			Some(ref val) => {
				if let TomlValue::Integer(int) = val {
					Ok(*int)
				} else {
					Err(TomlGetError::TypeMismatch(val, val.ty()))
				}
			}
		}
	}
	/// Gets the value for a key, if that value is a float.
	pub fn get_float(&self, key: &str) -> Result<f64, TomlGetError<'_, 'a>> {
		match self.get(key) {
			None => Err(TomlGetError::InvalidKey),
			Some(ref val) => {
				if let TomlValue::Float(float) = val {
					Ok(*float)
				} else {
					Err(TomlGetError::TypeMismatch(val, val.ty()))
				}
			}
		}
	}
	/// Gets the value for a key, if that value is a boolean.
	pub fn get_boolean(&self, key: &str) -> Result<bool, TomlGetError<'_, 'a>> {
		match self.get(key) {
			None => Err(TomlGetError::InvalidKey),
			Some(ref val) => {
				if let TomlValue::Boolean(bool) = val {
					Ok(*bool)
				} else {
					Err(TomlGetError::TypeMismatch(val, val.ty()))
				}
			}
		}
	}
	/// Gets the value for a key, if that value is an array.
	pub fn get_array(&self, key: &str) -> Result<&TomlArray<'a>, TomlGetError<'_, 'a>> {
		match self.get(key) {
			None => Err(TomlGetError::InvalidKey),
			Some(ref val) => {
				if let TomlValue::Array(array) = val {
					Ok(array)
				} else {
					Err(TomlGetError::TypeMismatch(val, val.ty()))
				}
			}
		}
	}

	pub(crate) fn value_entry<'b>(
		&'b mut self,
		text: &mut Text<'a>,
	) -> Result<VacantEntry<'b, CowSpan<'a>, TomlValue<'a>>, TomlError<'a>> {
		let start = text.idx();
		let (table, key) = crate::parser::key::parse_nested(text, self)?;

		match table.map.entry(key) {
			Entry::Occupied(_) => Err(TomlError {
				src: text.excerpt_to_idx(start..),
				kind: TomlErrorKind::ReusedKey,
			}),
			Entry::Vacant(vacant) => Ok(vacant),
		}
	}
}
impl<'a> Deref for TomlTable<'a> {
	type Target = HashMap<CowSpan<'a>, TomlValue<'a>>;

	fn deref(&self) -> &Self::Target {
		&self.map
	}
}

/// Errors for the `get_<type>` methods in [`TomlTable`].
#[derive(Debug, PartialEq)]
pub enum TomlGetError<'a, 'table> {
	/// There was no value for the provided key.
	InvalidKey,
	/// The value for the provided key had a different type. Stores the
	/// value for that key and its type.
	TypeMismatch(&'a TomlValue<'table>, TomlValueType),
}

#[cfg(test)]
mod tests {
	use super::*;

	struct Tester {
		key: &'static str,
		value: TomlValue<'static>,
	}
	impl Tester {
		fn build(self) -> TomlTable<'static> {
			println!("Running test for key `{}`", self.key);

			let mut table = TomlTable::default();
			table
				.value_entry(&mut Text::new(self.key))
				.unwrap()
				.insert(self.value);
			table
		}
	}

	#[test]
	fn test_table_keys() {
		let basic = Tester {
			key: "bool",
			value: TomlValue::Boolean(true),
		}
		.build();
		assert_eq!(basic.get("bool"), Some(&TomlValue::Boolean(true)));

		let dotted = Tester {
			key: "dot.bool",
			value: TomlValue::Boolean(true),
		}
		.build();
		let Some(TomlValue::Table(subtable)) = dotted.get("dot") else {
			panic!()
		};
		assert_eq!(subtable.get("bool"), Some(&TomlValue::Boolean(true)));

		let quoted = Tester {
			key: "'wowza.hi'",
			value: TomlValue::Boolean(true),
		}
		.build();
		assert_eq!(quoted.get("wowza.hi"), Some(&TomlValue::Boolean(true)));

		let quoted_alt = Tester {
			key: r#""wowza.hi""#,
			value: TomlValue::Boolean(true),
		}
		.build();
		assert_eq!(quoted_alt.get("wowza.hi"), Some(&TomlValue::Boolean(true)));
	}
}