blob: 6480ceada5fc66b831c903f623330689df4ae538 [file] [log] [blame]
/// The Toml Delete extensions
use toml::Value;
use crate::tokenizer::Token;
use crate::tokenizer::tokenize_with_seperator;
use crate::error::{Error, Result};
pub trait TomlValueDeleteExt {
/// Extension function for deleting a value in the current toml::Value document
/// using a custom seperator.
///
/// # Semantics
///
/// The function does _not_ delete non-empty data structures. So deleting `array` from
///
/// ```toml
/// array = [ 1 ]
/// ```
///
/// does _not_ work.
///
/// # Return value
///
/// If the delete operation worked correctly, `Ok(Option<Value>)` is returned.
///
/// The `Option<Value>` part is `None` if no value was actually removed as there was no value
/// there. For example, if you're deleting `table.a` and the Table `table` has no key `a`, then
/// `Ok(None)` is returned. Also, if you're deleting from an Array, but there is nothing in the
/// array, or the array is shorter than the index you're deleting.
/// If the delete operation actually removed something from the toml document, this value is
/// returned as `Ok(Some(Value))`.
///
/// On failure, `Err(e)` is returned
///
fn delete_with_seperator(&mut self, query: &str, sep: char) -> Result<Option<Value>>;
/// Extension function for inserting a value from the current toml::Value document
///
/// See documentation of `TomlValueinsertExt::insert_with_seperator`
fn delete(&mut self, query: &str) -> Result<Option<Value>> {
self.delete_with_seperator(query, '.')
}
}
impl TomlValueDeleteExt for Value {
fn delete_with_seperator(&mut self, query: &str, sep: char) -> Result<Option<Value>> {
use crate::resolver::mut_resolver::resolve;
use std::ops::Index;
let mut tokens = r#try!(tokenize_with_seperator(query, sep));
let last_token = tokens.pop_last();
/// Check whether a structure (Table/Array) is empty. If the Value has not these types,
/// the default value is returned
#[inline]
fn is_empty(val: Option<&Value>, default: bool) -> bool {
val.map(|v| match v {
&Value::Table(ref tab) => tab.is_empty(),
&Value::Array(ref arr) => arr.is_empty(),
_ => default
})
.unwrap_or(default)
}
#[inline]
fn is_table(val: Option<&Value>) -> bool {
val.map(|v| is_match!(v, &Value::Table(_))).unwrap_or(false)
}
#[inline]
fn is_array(val: Option<&Value>) -> bool {
val.map(|v| is_match!(v, &Value::Array(_))).unwrap_or(false)
}
#[inline]
fn name_of_val(val: Option<&Value>) -> &'static str {
val.map(crate::util::name_of_val).unwrap_or("None")
}
if last_token.is_none() {
match self {
&mut Value::Table(ref mut tab) => {
match tokens {
Token::Identifier { ident, .. } => {
if is_empty(tab.get(&ident), true) {
Ok(tab.remove(&ident))
} else {
if is_table(tab.get(&ident)) {
Err(Error::CannotDeleteNonEmptyTable(Some(ident.clone())))
} else if is_array(tab.get(&ident)) {
Err(Error::CannotDeleteNonEmptyArray(Some(ident.clone())))
} else {
let act = name_of_val(tab.get(&ident));
let tbl = "table";
Err(Error::CannotAccessBecauseTypeMismatch(tbl, act))
}
}
},
_ => Ok(None)
}
},
&mut Value::Array(ref mut arr) => {
match tokens {
Token::Identifier { ident, .. } => Err(Error::NoIdentifierInArray(ident)),
Token::Index { idx , .. } => {
if is_empty(Some(arr.index(idx)), true) {
Ok(Some(arr.remove(idx)))
} else {
if is_table(Some(arr.index(idx))) {
Err(Error::CannotDeleteNonEmptyTable(None))
} else if is_array(Some(arr.index(idx))) {
Err(Error::CannotDeleteNonEmptyArray(None))
} else {
let act = name_of_val(Some(arr.index(idx)));
let tbl = "table";
Err(Error::CannotAccessBecauseTypeMismatch(tbl, act))
}
}
},
}
},
_ => {
let kind = match tokens {
Token::Identifier { ident, .. } => Error::QueryingValueAsTable(ident),
Token::Index { idx , .. } => Error::QueryingValueAsArray(idx),
};
Err(Error::from(kind))
}
}
} else {
let val = r#try!(resolve(self, &tokens, true))
.unwrap(); // safe because of resolve() guarantees
let last_token = last_token.unwrap();
match val {
&mut Value::Table(ref mut tab) => {
match *last_token {
Token::Identifier { ref ident, .. } => {
if is_empty(tab.get(ident), true) {
Ok(tab.remove(ident))
} else {
if is_table(tab.get(ident)) {
Err(Error::CannotDeleteNonEmptyTable(Some(ident.clone())))
} else if is_array(tab.get(ident)) {
Err(Error::CannotDeleteNonEmptyArray(Some(ident.clone())))
} else {
let act = name_of_val(tab.get(ident));
let tbl = "table";
Err(Error::CannotAccessBecauseTypeMismatch(tbl, act))
}
}
},
Token::Index { idx, .. } => Err(Error::NoIndexInTable(idx)),
}
},
&mut Value::Array(ref mut arr) => {
match *last_token {
Token::Identifier { ident, .. } => Err(Error::NoIdentifierInArray(ident)),
Token::Index { idx, .. } => {
if idx > arr.len() {
return Err(Error::ArrayIndexOutOfBounds(idx, arr.len()))
}
if is_empty(Some(&arr.index(idx)), true) {
Ok(Some(arr.remove(idx)))
} else {
if is_table(Some(&arr.index(idx))) {
Err(Error::CannotDeleteNonEmptyTable(None))
} else if is_array(Some(&arr.index(idx))) {
Err(Error::CannotDeleteNonEmptyArray(None))
} else {
let act = name_of_val(Some(arr.index(idx)));
let tbl = "table";
Err(Error::CannotAccessBecauseTypeMismatch(tbl, act))
}
}
},
}
},
_ => {
let kind = match *last_token {
Token::Identifier { ident, .. } => Error::QueryingValueAsTable(ident),
Token::Index { idx, .. } => Error::QueryingValueAsArray(idx),
};
Err(Error::from(kind))
}
}
}
}
}
#[cfg(test)]
mod test {
use super::*;
use toml::Value;
use toml::from_str as toml_from_str;
#[test]
fn test_delete_from_empty_document() {
let mut toml : Value = toml_from_str("").unwrap();
let res = toml.delete_with_seperator(&String::from("a"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(res.is_none());
}
#[test]
fn test_delete_from_empty_table() {
let mut toml : Value = toml_from_str(r#"
[table]
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("table.a"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(res.is_none());
}
#[test]
fn test_delete_integer() {
let mut toml : Value = toml_from_str(r#"
value = 1
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("value"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(res.is_some());
let res = res.unwrap();
assert!(is_match!(res, Value::Integer(1)));
}
#[test]
fn test_delete_integer_removes_entry_from_document() {
let mut toml : Value = toml_from_str(r#"
value = 1
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("value"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(res.is_some());
let res = res.unwrap();
assert!(is_match!(res, Value::Integer(1)));
match toml {
Value::Table(tab) => assert!(tab.is_empty()),
_ => assert!(false, "Strange things are happening"),
}
}
#[test]
fn test_delete_string() {
let mut toml : Value = toml_from_str(r#"
value = "foo"
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("value"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(res.is_some());
let res = res.unwrap();
assert!(is_match!(res, Value::String(_)));
match res {
Value::String(ref s) => assert_eq!("foo", s),
_ => panic!("What just happened?"),
}
}
#[test]
fn test_delete_string_removes_entry_from_document() {
let mut toml : Value = toml_from_str(r#"
value = "foo"
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("value"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(res.is_some());
let res = res.unwrap();
assert!(is_match!(res, Value::String(_)));
match res {
Value::String(ref s) => assert_eq!("foo", s),
_ => panic!("What just happened?"),
}
match toml {
Value::Table(tab) => assert!(tab.is_empty()),
_ => assert!(false, "Strange things are happening"),
}
}
#[test]
fn test_delete_empty_table() {
let mut toml : Value = toml_from_str(r#"
[table]
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("table"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(res.is_some());
let res = res.unwrap();
assert!(is_match!(res, Value::Table(_)));
match res {
Value::Table(ref t) => assert!(t.is_empty()),
_ => panic!("What just happened?"),
}
}
#[test]
fn test_delete_empty_table_removes_entry_from_document() {
let mut toml : Value = toml_from_str(r#"
[table]
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("table"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(res.is_some());
let res = res.unwrap();
assert!(is_match!(res, Value::Table(_)));
match res {
Value::Table(ref t) => assert!(t.is_empty()),
_ => panic!("What just happened?"),
}
match toml {
Value::Table(tab) => assert!(tab.is_empty()),
_ => assert!(false, "Strange things are happening"),
}
}
#[test]
fn test_delete_empty_array() {
let mut toml : Value = toml_from_str(r#"
array = []
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("array"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(res.is_some());
let res = res.unwrap();
assert!(is_match!(res, Value::Array(_)));
match res {
Value::Array(ref a) => assert!(a.is_empty()),
_ => panic!("What just happened?"),
}
}
#[test]
fn test_delete_empty_array_removes_entry_from_document() {
let mut toml : Value = toml_from_str(r#"
array = []
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("array"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(res.is_some());
let res = res.unwrap();
assert!(is_match!(res, Value::Array(_)));
match res {
Value::Array(ref a) => assert!(a.is_empty()),
_ => panic!("What just happened?"),
}
match toml {
Value::Table(tab) => assert!(tab.is_empty()),
_ => assert!(false, "Strange things are happening"),
}
}
#[test]
fn test_delete_nonempty_table() {
let mut toml : Value = toml_from_str(r#"
[table]
a = 1
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("table"), '.');
assert!(res.is_err());
let res = res.unwrap_err();
assert!(is_match!(res, Error::CannotDeleteNonEmptyTable(_)));
}
#[test]
fn test_delete_nonempty_array() {
let mut toml : Value = toml_from_str(r#"
array = [ 1 ]
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("array"), '.');
assert!(res.is_err());
let res = res.unwrap_err();
assert!(is_match!(res, Error::CannotDeleteNonEmptyArray(_)));
}
#[test]
fn test_delete_int_from_table() {
let mut toml : Value = toml_from_str(r#"
[table]
int = 1
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("table.int"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(is_match!(res, Some(Value::Integer(1))));
}
#[test]
fn test_delete_array_from_table() {
let mut toml : Value = toml_from_str(r#"
[table]
array = []
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("table.array"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(is_match!(res, Some(Value::Array(_))));
}
#[test]
fn test_delete_int_from_array_from_table() {
let mut toml : Value = toml_from_str(r#"
[table]
array = [ 1 ]
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("table.array.[0]"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(is_match!(res, Some(Value::Integer(1))));
}
#[test]
fn test_delete_int_from_array() {
let mut toml : Value = toml_from_str(r#"
array = [ 1 ]
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("array.[0]"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(is_match!(res, Some(Value::Integer(1))));
}
#[test]
fn test_delete_int_from_table_from_array() {
let mut toml : Value = toml_from_str(r#"
array = [ { table = { int = 1 } } ]
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("array.[0].table.int"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(is_match!(res, Some(Value::Integer(1))));
}
#[test]
fn test_delete_from_array_value() {
use crate::read::TomlValueReadExt;
let mut toml : Value = toml_from_str(r#"
array = [ 1 ]
"#).unwrap();
let ary = toml.read_mut(&String::from("array")).unwrap().unwrap();
let res = ary.delete_with_seperator(&String::from("[0]"), '.');
assert!(res.is_ok());
let res = res.unwrap();
assert!(is_match!(res, Some(Value::Integer(1))));
}
#[test]
fn test_delete_from_int_value() {
use crate::read::TomlValueReadExt;
let mut toml : Value = toml_from_str(r#"
array = [ 1 ]
"#).unwrap();
let ary = toml.read_mut(&String::from("array.[0]")).unwrap().unwrap();
let res = ary.delete_with_seperator(&String::from("nonexist"), '.');
assert!(res.is_err());
let res = res.unwrap_err();
assert!(is_match!(res, Error::QueryingValueAsTable(_)));
}
#[test]
fn test_delete_index_from_non_array() {
use crate::read::TomlValueReadExt;
let mut toml : Value = toml_from_str(r#"
array = 1
"#).unwrap();
let ary = toml.read_mut(&String::from("array")).unwrap().unwrap();
let res = ary.delete_with_seperator(&String::from("[0]"), '.');
assert!(res.is_err());
let res = res.unwrap_err();
assert!(is_match!(res, Error::QueryingValueAsArray(_)));
}
#[test]
fn test_delete_index_from_table_in_table() {
let mut toml : Value = toml_from_str(r#"
table = { another = { int = 1 } }
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("table.another.[0]"), '.');
assert!(res.is_err());
let res = res.unwrap_err();
assert!(is_match!(res, Error::NoIndexInTable(0)));
}
#[test]
fn test_delete_identifier_from_array_in_table() {
let mut toml : Value = toml_from_str(r#"
table = { another = [ 1, 2, 3, 4, 5, 6 ] }
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("table.another.nonexist"), '.');
assert!(res.is_err());
let res = res.unwrap_err();
assert!(is_match!(res, Error::NoIdentifierInArray(_)));
}
#[test]
fn test_delete_nonexistent_array_idx() {
let mut toml : Value = toml_from_str(r#"
array = [ 1, 2, 3 ]
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("array.[22]"), '.');
assert!(res.is_err());
let res = res.unwrap_err();
assert!(is_match!(res, Error::ArrayIndexOutOfBounds(22, 3)));
}
#[test]
fn test_delete_non_empty_array_from_array() {
let mut toml : Value = toml_from_str(r#"
array = [ [ 1 ], [ 2 ] ]
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("array.[1]"), '.');
assert!(res.is_err());
let res = res.unwrap_err();
assert!(is_match!(res, Error::CannotDeleteNonEmptyArray(None)));
}
#[test]
fn test_delete_non_empty_table_from_array() {
let mut toml : Value = toml_from_str(r#"
array = [ { t = 1 }, { t = 2 } ]
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("array.[1]"), '.');
assert!(res.is_err());
let res = res.unwrap_err();
assert!(is_match!(res, Error::CannotDeleteNonEmptyTable(None)));
}
#[test]
fn test_delete_non_empty_table_from_top_level_array() {
use crate::read::TomlValueReadExt;
let mut toml : Value = toml_from_str(r#"
array = [ { t = 1 }, { t = 2 } ]
"#).unwrap();
let ary = toml.read_mut(&String::from("array")).unwrap().unwrap();
let res = ary.delete_with_seperator(&String::from("[1]"), '.');
assert!(res.is_err());
let res = res.unwrap_err();
assert!(is_match!(res, Error::CannotDeleteNonEmptyTable(None)));
}
#[test]
fn test_delete_from_value_like_it_was_table() {
let mut toml : Value = toml_from_str(r#"
val = 5
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("val.foo"), '.');
assert!(res.is_err());
let res = res.unwrap_err();
assert!(is_match!(res, Error::QueryingValueAsTable(_)));
}
#[test]
fn test_delete_from_value_like_it_was_array() {
let mut toml : Value = toml_from_str(r#"
val = 5
"#).unwrap();
let res = toml.delete_with_seperator(&String::from("val.[0]"), '.');
assert!(res.is_err());
let res = res.unwrap_err();
assert!(is_match!(res, Error::QueryingValueAsArray(0)));
}
}