blob: 14b6aa6fb77d17d8e5dfdbc840eea13940581664 [file] [log] [blame]
//! Contains the [`Value`] and [`ValueInner`] containers into which all toml
//! contents can be deserialized into and either used directly or fed into
//! [`crate::Deserialize`] or your own constructs to deserialize into your own
//! types
use crate::{Error, ErrorKind, Span};
use std::{borrow::Cow, fmt};
/// A deserialized [`ValueInner`] with accompanying [`Span`] information for where
/// it was located in the toml document
pub struct Value<'de> {
value: Option<ValueInner<'de>>,
/// The location of the value in the toml document
pub span: Span,
}
impl<'de> Value<'de> {
/// Creates a new [`Value`] with an empty [`Span`]
#[inline]
pub fn new(value: ValueInner<'de>) -> Self {
Self::with_span(value, Span::default())
}
/// Creates a new [`Value`] with the specified [`Span`]
#[inline]
pub fn with_span(value: ValueInner<'de>, span: Span) -> Self {
Self {
value: Some(value),
span,
}
}
/// Takes the inner [`ValueInner`]
///
/// This panics if the inner value has already been taken.
///
/// Typically paired with [`Self::set`]
#[inline]
pub fn take(&mut self) -> ValueInner<'de> {
self.value.take().expect("the value has already been taken")
}
/// Sets the inner [`ValueInner`]
///
/// This is typically done when the value is taken with [`Self::take`],
/// processed, and returned
#[inline]
pub fn set(&mut self, value: ValueInner<'de>) {
self.value = Some(value);
}
/// Returns true if the value is a table and is non-empty
#[inline]
pub fn has_keys(&self) -> bool {
self.value.as_ref().map_or(false, |val| {
if let ValueInner::Table(table) = val {
!table.is_empty()
} else {
false
}
})
}
/// Returns true if the value is a table and has the specified key
#[inline]
pub fn has_key(&self, key: &str) -> bool {
self.value.as_ref().map_or(false, |val| {
if let ValueInner::Table(table) = val {
table.contains_key(&key.into())
} else {
false
}
})
}
/// Takes the value as a string, returning an error with either a default
/// or user supplied message
#[inline]
pub fn take_string(&mut self, msg: Option<&'static str>) -> Result<Cow<'de, str>, Error> {
match self.take() {
ValueInner::String(s) => Ok(s),
other => Err(Error {
kind: ErrorKind::Wanted {
expected: msg.unwrap_or("a string"),
found: other.type_str(),
},
span: self.span,
line_info: None,
}),
}
}
/// Returns a borrowed string if this is a [`ValueInner::String`]
#[inline]
pub fn as_str(&self) -> Option<&str> {
self.value.as_ref().and_then(|v| v.as_str())
}
/// Returns a borrowed table if this is a [`ValueInner::Table`]
#[inline]
pub fn as_table(&self) -> Option<&Table<'de>> {
self.value.as_ref().and_then(|v| v.as_table())
}
/// Returns a borrowed array if this is a [`ValueInner::Array`]
#[inline]
pub fn as_array(&self) -> Option<&Array<'de>> {
self.value.as_ref().and_then(|v| v.as_array())
}
/// Returns an `i64` if this is a [`ValueInner::Integer`]
#[inline]
pub fn as_integer(&self) -> Option<i64> {
self.value.as_ref().and_then(|v| v.as_integer())
}
/// Returns an `f64` if this is a [`ValueInner::Float`]
#[inline]
pub fn as_float(&self) -> Option<f64> {
self.value.as_ref().and_then(|v| v.as_float())
}
/// Returns a `bool` if this is a [`ValueInner::Boolean`]
#[inline]
pub fn as_bool(&self) -> Option<bool> {
self.value.as_ref().and_then(|v| v.as_bool())
}
/// Uses JSON pointer-like syntax to lookup a specific [`Value`]
///
/// The basic format is:
///
/// - The path starts with `/`
/// - Each segment is separated by a `/`
/// - Each segment is either a key name, or an integer array index
///
/// ```rust
/// let data = "[x]\ny = ['z', 'zz']";
/// let value = toml_span::parse(data).unwrap();
/// assert_eq!(value.pointer("/x/y/1").unwrap().as_str().unwrap(), "zz");
/// assert!(value.pointer("/a/b/c").is_none());
/// ```
///
/// Note that this is JSON pointer**-like** because `/` is not supported in
/// key names because I don't see the point. If you want this it is easy to
/// implement.
pub fn pointer(&self, pointer: &'de str) -> Option<&Self> {
if pointer.is_empty() {
return Some(self);
} else if !pointer.starts_with('/') {
return None;
}
pointer
.split('/')
.skip(1)
// Don't support / or ~ in key names unless someone actually opens
// an issue about it
//.map(|x| x.replace("~1", "/").replace("~0", "~"))
.try_fold(self, |target, token| {
(match &target.value {
Some(ValueInner::Table(tab)) => tab.get(&token.into()),
Some(ValueInner::Array(list)) => parse_index(token).and_then(|x| list.get(x)),
_ => None,
})
.filter(|v| v.value.is_some())
})
}
/// The `mut` version of [`Self::pointer`]
pub fn pointer_mut(&mut self, pointer: &'de str) -> Option<&mut Self> {
if pointer.is_empty() {
return Some(self);
} else if !pointer.starts_with('/') {
return None;
}
pointer
.split('/')
.skip(1)
// Don't support / or ~ in key names unless someone actually opens
// an issue about it
//.map(|x| x.replace("~1", "/").replace("~0", "~"))
.try_fold(self, |target, token| {
(match &mut target.value {
Some(ValueInner::Table(tab)) => tab.get_mut(&token.into()),
Some(ValueInner::Array(list)) => {
parse_index(token).and_then(|x| list.get_mut(x))
}
_ => None,
})
.filter(|v| v.value.is_some())
})
}
}
fn parse_index(s: &str) -> Option<usize> {
if s.starts_with('+') || (s.starts_with('0') && s.len() != 1) {
return None;
}
s.parse().ok()
}
impl<'de> AsRef<ValueInner<'de>> for Value<'de> {
fn as_ref(&self) -> &ValueInner<'de> {
self.value
.as_ref()
.expect("the value has already been taken")
}
}
impl<'de> fmt::Debug for Value<'de> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{:?}", self.value)
}
}
/// A toml table key
#[derive(Clone)]
pub struct Key<'de> {
/// The key itself, in most cases it will be borrowed, but may be owned
/// if escape characters are present in the original source
pub name: Cow<'de, str>,
/// The span for the key in the original document
pub span: Span,
}
impl<'de> From<&'de str> for Key<'de> {
fn from(k: &'de str) -> Self {
Self {
name: Cow::Borrowed(k),
span: Span::default(),
}
}
}
impl<'de> fmt::Debug for Key<'de> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "{}", self.name)
}
}
impl<'de> Ord for Key<'de> {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.name.cmp(&other.name)
}
}
impl<'de> PartialOrd for Key<'de> {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl<'de> PartialEq for Key<'de> {
fn eq(&self, other: &Self) -> bool {
self.name.eq(&other.name)
}
}
impl<'de> Eq for Key<'de> {}
/// A toml table, always represented as a sorted map.
///
/// The original key ordering can be obtained by ordering the keys by their span
pub type Table<'de> = std::collections::BTreeMap<Key<'de>, Value<'de>>;
/// A toml array
pub type Array<'de> = Vec<Value<'de>>;
/// The core value types that toml can deserialize to
///
/// Note that this library does not support datetime values that are part of the
/// toml spec since I have no need of them, but could be added
#[derive(Debug)]
pub enum ValueInner<'de> {
/// A string.
///
/// This will be borrowed from the original toml source unless it contains
/// escape characters
String(Cow<'de, str>),
/// An integer
Integer(i64),
/// A float
Float(f64),
/// A boolean
Boolean(bool),
/// An array
Array(Array<'de>),
/// A table
Table(Table<'de>),
}
impl<'de> ValueInner<'de> {
/// Gets the type of the value as a string
pub fn type_str(&self) -> &'static str {
match self {
Self::String(..) => "string",
Self::Integer(..) => "integer",
Self::Float(..) => "float",
Self::Boolean(..) => "boolean",
Self::Array(..) => "array",
Self::Table(..) => "table",
}
}
/// Returns a borrowed string if this is a [`Self::String`]
#[inline]
pub fn as_str(&self) -> Option<&str> {
if let Self::String(s) = self {
Some(s.as_ref())
} else {
None
}
}
/// Returns a borrowed table if this is a [`Self::Table`]
#[inline]
pub fn as_table(&self) -> Option<&Table<'de>> {
if let ValueInner::Table(t) = self {
Some(t)
} else {
None
}
}
/// Returns a borrowed array if this is a [`Self::Array`]
#[inline]
pub fn as_array(&self) -> Option<&Array<'de>> {
if let ValueInner::Array(a) = self {
Some(a)
} else {
None
}
}
/// Returns an `i64` if this is a [`Self::Integer`]
#[inline]
pub fn as_integer(&self) -> Option<i64> {
if let ValueInner::Integer(i) = self {
Some(*i)
} else {
None
}
}
/// Returns an `f64` if this is a [`Self::Float`]
#[inline]
pub fn as_float(&self) -> Option<f64> {
if let ValueInner::Float(f) = self {
Some(*f)
} else {
None
}
}
/// Returns a `bool` if this is a [`Self::Boolean`]
#[inline]
pub fn as_bool(&self) -> Option<bool> {
if let ValueInner::Boolean(b) = self {
Some(*b)
} else {
None
}
}
}