Skip to content

Commit c82013e

Browse files
committed
fix: Support Yaml values
- Required switching to `serde_yaml`. - Like with `yaml-rust`, requires special handling for table keys. Signed-off-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
1 parent e597f6a commit c82013e

File tree

7 files changed

+53
-102
lines changed

7 files changed

+53
-102
lines changed

Cargo.toml

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ maintenance = { status = "actively-developed" }
1717
[features]
1818
default = ["toml", "json", "yaml", "ini", "ron", "json5", "convert-case", "async"]
1919
json = ["serde_json"]
20-
yaml = ["yaml-rust"]
20+
yaml = ["serde_yaml"]
2121
ini = ["rust-ini"]
2222
json5 = ["json5_rs", "serde/derive"]
2323
convert-case = ["convert_case"]
@@ -33,7 +33,7 @@ nom = "7"
3333
async-trait = { version = "0.1.50", optional = true }
3434
toml = { version = "0.8", optional = true }
3535
serde_json = { version = "1.0.2", optional = true }
36-
yaml-rust = { version = "0.4", optional = true }
36+
serde_yaml = { version = "0.9", optional = true }
3737
rust-ini = { version = "0.19", optional = true }
3838
ron = { version = "0.8", optional = true }
3939
json5_rs = { version = "0.4", optional = true, package = "json5" }

README.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818

1919
[JSON]: https://github.com/serde-rs/json
2020
[TOML]: https://github.com/toml-lang/toml
21-
[YAML]: https://github.com/chyh1990/yaml-rust
21+
[YAML]: https://github.com/dtolnay/serde-yaml
2222
[INI]: https://github.com/zonyitoo/rust-ini
2323
[RON]: https://github.com/ron-rs/ron
2424
[JSON5]: https://github.com/callum-oakley/json5-rs

src/file/format/mod.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ pub enum FileFormat {
4040
#[cfg(feature = "json")]
4141
Json,
4242

43-
/// YAML (parsed with yaml_rust)
43+
/// YAML (parsed with serde_yaml)
4444
#[cfg(feature = "yaml")]
4545
Yaml,
4646

src/file/format/yaml.rs

+3-94
Original file line numberDiff line numberDiff line change
@@ -1,105 +1,14 @@
11
use std::error::Error;
2-
use std::fmt;
3-
use std::mem;
4-
5-
use yaml_rust as yaml;
62

73
use crate::format;
84
use crate::map::Map;
9-
use crate::value::{Value, ValueKind};
5+
use crate::value::Value;
106

117
pub fn parse(
128
uri: Option<&String>,
139
text: &str,
1410
) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
15-
// Parse a YAML object from file
16-
let mut docs = yaml::YamlLoader::load_from_str(text)?;
17-
let root = match docs.len() {
18-
0 => yaml::Yaml::Hash(yaml::yaml::Hash::new()),
19-
1 => mem::replace(&mut docs[0], yaml::Yaml::Null),
20-
n => {
21-
return Err(Box::new(MultipleDocumentsError(n)));
22-
}
23-
};
24-
25-
let value = from_yaml_value(uri, &root)?;
11+
// Parse a YAML input from the provided text
12+
let value = format::from_parsed_value(uri, serde_yaml::from_str(text)?);
2613
format::extract_root_table(uri, value)
2714
}
28-
29-
fn from_yaml_value(
30-
uri: Option<&String>,
31-
value: &yaml::Yaml,
32-
) -> Result<Value, Box<dyn Error + Send + Sync>> {
33-
match *value {
34-
yaml::Yaml::String(ref value) => Ok(Value::new(uri, ValueKind::String(value.clone()))),
35-
yaml::Yaml::Real(ref value) => {
36-
// TODO: Figure out in what cases this can panic?
37-
value
38-
.parse::<f64>()
39-
.map_err(|_| {
40-
Box::new(FloatParsingError(value.to_string())) as Box<(dyn Error + Send + Sync)>
41-
})
42-
.map(ValueKind::Float)
43-
.map(|f| Value::new(uri, f))
44-
}
45-
yaml::Yaml::Integer(value) => Ok(Value::new(uri, ValueKind::I64(value))),
46-
yaml::Yaml::Boolean(value) => Ok(Value::new(uri, ValueKind::Boolean(value))),
47-
yaml::Yaml::Hash(ref table) => {
48-
let mut m = Map::new();
49-
for (key, value) in table {
50-
match key {
51-
yaml::Yaml::String(k) => m.insert(k.to_owned(), from_yaml_value(uri, value)?),
52-
yaml::Yaml::Integer(k) => m.insert(k.to_string(), from_yaml_value(uri, value)?),
53-
_ => unreachable!(),
54-
};
55-
}
56-
Ok(Value::new(uri, ValueKind::Table(m)))
57-
}
58-
yaml::Yaml::Array(ref array) => {
59-
let mut l = Vec::new();
60-
61-
for value in array {
62-
l.push(from_yaml_value(uri, value)?);
63-
}
64-
65-
Ok(Value::new(uri, ValueKind::Array(l)))
66-
}
67-
68-
// 1. Yaml NULL
69-
// 2. BadValue – It shouldn't be possible to hit BadValue as this only happens when
70-
// using the index trait badly or on a type error but we send back nil.
71-
// 3. Alias – No idea what to do with this and there is a note in the lib that its
72-
// not fully supported yet anyway
73-
_ => Ok(Value::new(uri, ValueKind::Nil)),
74-
}
75-
}
76-
77-
#[derive(Debug, Copy, Clone)]
78-
struct MultipleDocumentsError(usize);
79-
80-
impl fmt::Display for MultipleDocumentsError {
81-
fn fmt(&self, format: &mut fmt::Formatter) -> fmt::Result {
82-
write!(format, "Got {} YAML documents, expected 1", self.0)
83-
}
84-
}
85-
86-
impl Error for MultipleDocumentsError {
87-
fn description(&self) -> &str {
88-
"More than one YAML document provided"
89-
}
90-
}
91-
92-
#[derive(Debug, Clone)]
93-
struct FloatParsingError(String);
94-
95-
impl fmt::Display for FloatParsingError {
96-
fn fmt(&self, format: &mut fmt::Formatter) -> fmt::Result {
97-
write!(format, "Parsing {} as floating point number failed", self.0)
98-
}
99-
}
100-
101-
impl Error for FloatParsingError {
102-
fn description(&self) -> &str {
103-
"Floating point number parsing failed"
104-
}
105-
}

src/format.rs

+40
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ pub enum ParsedValue {
6262
Float(f64),
6363
#[serde(deserialize_with = "deserialize_parsed_string")]
6464
String(String),
65+
#[serde(deserialize_with = "deserialize_parsed_map")]
6566
Table(Map<String, Self>),
6667
Array(Vec<Self>),
6768
Option(Option<Box<Self>>),
@@ -139,3 +140,42 @@ where
139140
},
140141
}
141142
}
143+
144+
fn deserialize_parsed_map<'de, D>(deserializer: D) -> Result<Map<String, ParsedValue>, D::Error>
145+
where
146+
D: serde::de::Deserializer<'de>,
147+
{
148+
#[derive(serde::Deserialize)]
149+
#[serde(untagged)]
150+
enum ParsedMap {
151+
// Anything that can deserialize into a Map successfully:
152+
Table(Map<String, ParsedValue>),
153+
// Config specific support for types that need string conversion:
154+
#[cfg(feature = "yaml")]
155+
YamlMap(serde_yaml::Mapping),
156+
}
157+
158+
match ParsedMap::deserialize(deserializer)? {
159+
ParsedMap::Table(v) => Ok(v),
160+
#[cfg(feature = "yaml")]
161+
ParsedMap::YamlMap(table) => {
162+
table
163+
.into_iter()
164+
.map(|(key, value)| {
165+
let key = match key {
166+
serde_yaml::Value::Number(k) => Some(k.to_string()),
167+
serde_yaml::Value::String(k) => k,
168+
_ => None,
169+
};
170+
let value = serde_yaml::from_value::<ParsedValue>(value).ok();
171+
172+
// Option to Result:
173+
match (key, value) {
174+
(Some(k), Some(v)) => Ok((k, v)),
175+
_ => Err(serde::de::Error::custom("should not be serialized to Map")),
176+
}
177+
})
178+
.collect()
179+
}
180+
}
181+
}

tests/file_yaml.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,13 @@ fn test_error_parse() {
8181

8282
let path_with_extension: PathBuf = ["tests", "Settings-invalid.yaml"].iter().collect();
8383

84+
// Should fail to parse block mapping as no `:` exists to identify a key
8485
assert!(res.is_err());
8586
assert_eq!(
8687
res.unwrap_err().to_string(),
8788
format!(
88-
"while parsing a block mapping, did not find expected key at \
89-
line 2 column 1 in {}",
89+
"could not find expected ':' at line 3 column 1, \
90+
while scanning a simple key at line 2 column 1 in {}",
9091
path_with_extension.display()
9192
)
9293
);

tests/legacy/file_yaml.rs

+3-2
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,13 @@ fn test_error_parse() {
8181

8282
let path_with_extension: PathBuf = ["tests", "Settings-invalid.yaml"].iter().collect();
8383

84+
// Should fail to parse block mapping as no `:` exists to identify a key
8485
assert!(res.is_err());
8586
assert_eq!(
8687
res.unwrap_err().to_string(),
8788
format!(
88-
"while parsing a block mapping, did not find expected key at \
89-
line 2 column 1 in {}",
89+
"could not find expected ':' at line 3 column 1, \
90+
while scanning a simple key at line 2 column 1 in {}",
9091
path_with_extension.display()
9192
)
9293
);

0 commit comments

Comments
 (0)