aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/de.rs62
-rw-r--r--test-suite/tests/invalid-misc.rs8
-rw-r--r--test-suite/tests/valid.rs5
-rw-r--r--test-suite/tests/valid/dotted-keys.json44
-rw-r--r--test-suite/tests/valid/dotted-keys.toml9
5 files changed, 122 insertions, 6 deletions
diff --git a/src/de.rs b/src/de.rs
index 85009ed..d41ca12 100644
--- a/src/de.rs
+++ b/src/de.rs
@@ -159,6 +159,9 @@ enum ErrorKind {
/// A struct was expected but something else was found
ExpectedString,
+ /// Dotted key attempted to extend something that is not a table.
+ DottedKeyInvalidType,
+
#[doc(hidden)]
__Nonexhaustive,
}
@@ -210,7 +213,7 @@ impl<'de, 'b> de::Deserializer<'de> for &'b mut Deserializer<'de> {
if cur_table.values.is_none() {
cur_table.values = Some(Vec::new());
}
- cur_table.values.as_mut().unwrap().push((key, value));
+ self.add_dotted_key(key, value, cur_table.values.as_mut().unwrap())?;
}
}
}
@@ -796,7 +799,7 @@ impl<'a> Deserializer<'a> {
}
fn key_value(&mut self) -> Result<Line<'a>, Error> {
- let key = self.table_key()?;
+ let key = self.dotted_key()?;
self.eat_whitespace()?;
self.expect(Token::Equals)?;
self.eat_whitespace()?;
@@ -1095,11 +1098,12 @@ impl<'a> Deserializer<'a> {
return Ok((span, ret))
}
loop {
- let key = self.table_key()?;
+ let key = self.dotted_key()?;
self.eat_whitespace()?;
self.expect(Token::Equals)?;
self.eat_whitespace()?;
- ret.push((key, self.value()?));
+ let value = self.value()?;
+ self.add_dotted_key(key, value, &mut ret)?;
self.eat_whitespace()?;
if let Some(span) = self.eat_spanned(Token::RightBrace)? {
@@ -1152,6 +1156,52 @@ impl<'a> Deserializer<'a> {
self.tokens.table_key().map(|t| t.1).map_err(|e| self.token_error(e))
}
+ fn dotted_key(&mut self) -> Result<Vec<Cow<'a, str>>, Error> {
+ let mut result = Vec::new();
+ result.push(self.table_key()?);
+ self.eat_whitespace()?;
+ while self.eat(Token::Period)? {
+ self.eat_whitespace()?;
+ result.push(self.table_key()?);
+ self.eat_whitespace()?;
+ }
+ Ok(result)
+ }
+
+ fn add_dotted_key(
+ &self,
+ mut key_parts: Vec<Cow<'a, str>>,
+ value: Value<'a>,
+ values: &mut Vec<(Cow<'a, str>, Value<'a>)>,
+ ) -> Result<(), Error> {
+ let key = key_parts.remove(0);
+ if key_parts.is_empty() {
+ values.push((key, value));
+ return Ok(());
+ }
+ match values.iter_mut().find(|(k, _)| *k == key) {
+ Some((_, Value { e: E::InlineTable(ref mut v), .. })) => {
+ return self.add_dotted_key(key_parts, value, v);
+ }
+ Some((_, Value { start, .. })) => {
+ return Err(self.error(*start, ErrorKind::DottedKeyInvalidType));
+ }
+ None => {}
+ }
+ // The start/end value is somewhat misleading here.
+ let inline_table = Value {
+ e: E::InlineTable(Vec::new()),
+ start: value.start,
+ end: value.end,
+ };
+ values.push((key, inline_table));
+ let last_i = values.len() - 1;
+ if let (_, Value { e: E::InlineTable(ref mut v), .. }) = values[last_i] {
+ self.add_dotted_key(key_parts, value, v)?;
+ }
+ Ok(())
+ }
+
fn eat_whitespace(&mut self) -> Result<(), Error> {
self.tokens.eat_whitespace().map_err(|e| self.token_error(e))
}
@@ -1329,6 +1379,7 @@ impl fmt::Display for Error {
ErrorKind::EmptyTableKey => "empty table key found".fmt(f)?,
ErrorKind::Custom => self.inner.message.fmt(f)?,
ErrorKind::ExpectedString => "expected string".fmt(f)?,
+ ErrorKind::DottedKeyInvalidType => "dotted key attempted to extend non-table type".fmt(f)?,
ErrorKind::__Nonexhaustive => panic!(),
}
@@ -1372,6 +1423,7 @@ impl error::Error for Error {
ErrorKind::EmptyTableKey => "empty table key found",
ErrorKind::Custom => "a custom error",
ErrorKind::ExpectedString => "expected string",
+ ErrorKind::DottedKeyInvalidType => "dotted key invalid type",
ErrorKind::__Nonexhaustive => panic!(),
}
}
@@ -1385,7 +1437,7 @@ impl de::Error for Error {
enum Line<'a> {
Table { at: usize, header: Header<'a>, array: bool },
- KeyValue(Cow<'a, str>, Value<'a>),
+ KeyValue(Vec<Cow<'a, str>>, Value<'a>),
}
struct Header<'a> {
diff --git a/test-suite/tests/invalid-misc.rs b/test-suite/tests/invalid-misc.rs
index 80421e3..5c7f779 100644
--- a/test-suite/tests/invalid-misc.rs
+++ b/test-suite/tests/invalid-misc.rs
@@ -17,4 +17,12 @@ fn bad() {
bad("a = -0x1");
bad("a = 0x-1");
+
+ // Dotted keys.
+ bad("a.b.c = 1
+ a.b = 2
+ ");
+ bad("a = 1
+ a.b = 2");
+ bad("a = {k1 = 1, k1.name = \"joe\"}")
}
diff --git a/test-suite/tests/valid.rs b/test-suite/tests/valid.rs
index b186800..d032ba5 100644
--- a/test-suite/tests/valid.rs
+++ b/test-suite/tests/valid.rs
@@ -45,7 +45,7 @@ fn to_json(toml: toml::Value) -> Json {
fn run_pretty(toml: Toml) {
// Assert toml == json
println!("### pretty round trip parse.");
-
+
// standard pretty
let toml_raw = to_string_pretty(&toml).expect("to string");
let toml2 = toml_raw.parse().expect("from string");
@@ -247,3 +247,6 @@ test!(key_quote_newline,
test!(table_array_nest_no_keys,
include_str!("valid/table-array-nest-no-keys.toml"),
include_str!("valid/table-array-nest-no-keys.json"));
+test!(dotted_keys,
+ include_str!("valid/dotted-keys.toml"),
+ include_str!("valid/dotted-keys.json"));
diff --git a/test-suite/tests/valid/dotted-keys.json b/test-suite/tests/valid/dotted-keys.json
new file mode 100644
index 0000000..e32079a
--- /dev/null
+++ b/test-suite/tests/valid/dotted-keys.json
@@ -0,0 +1,44 @@
+{
+ "a": {
+ "b": {
+ "type": "integer",
+ "value": "123"
+ }
+ },
+ "table": {
+ "a": {
+ "b": {
+ "c": {
+ "type": "integer",
+ "value": "1"
+ },
+ "d": {
+ "type": "integer",
+ "value": "2"
+ }
+ }
+ },
+ "in": {
+ "bar": {
+ "type": "integer",
+ "value": "2"
+ },
+ "foo": {
+ "type": "integer",
+ "value": "1"
+ }
+ },
+ "in2": {
+ "type": {
+ "color": {
+ "type": "string",
+ "value": "blue"
+ },
+ "name": {
+ "type": "string",
+ "value": "cat"
+ }
+ }
+ }
+ }
+}
diff --git a/test-suite/tests/valid/dotted-keys.toml b/test-suite/tests/valid/dotted-keys.toml
new file mode 100644
index 0000000..27dbd32
--- /dev/null
+++ b/test-suite/tests/valid/dotted-keys.toml
@@ -0,0 +1,9 @@
+a.b = 123
+
+[table]
+a.b.c = 1
+a . b . d = 2
+in = {foo = 1}
+in.bar = 2
+
+in2 = { type.name = "cat", type.color = "blue" }