Skip to content

Commit

Permalink
fix: Change YAML crate to serde_yaml
Browse files Browse the repository at this point in the history
- `yaml-rust` is unmaintained for over 2 years.
- Like with `yaml-rust`, requires special handling for table keys.

Signed-off-by: Brennan Kinney <5098581+polarathene@users.noreply.github.com>
  • Loading branch information
polarathene committed Oct 10, 2023
1 parent 55c464e commit b815062
Show file tree
Hide file tree
Showing 6 changed files with 48 additions and 91 deletions.
4 changes: 2 additions & 2 deletions Cargo.toml
Expand Up @@ -17,7 +17,7 @@ maintenance = { status = "actively-developed" }
[features]
default = ["toml", "json", "yaml", "ini", "ron", "json5", "convert-case", "async"]
json = ["serde_json"]
yaml = ["yaml-rust"]
yaml = ["serde_yaml"]
ini = ["rust-ini"]
json5 = ["json5_rs", "serde/derive"]
convert-case = ["convert_case"]
Expand All @@ -32,7 +32,7 @@ nom = "7"
async-trait = { version = "0.1.50", optional = true }
toml = { version = "0.8", optional = true }
serde_json = { version = "1.0.2", optional = true }
yaml-rust = { version = "0.4", optional = true }
serde_yaml = { version = "0.9", optional = true }
rust-ini = { version = "0.19", optional = true }
ron = { version = "0.8", optional = true }
json5_rs = { version = "0.4", optional = true, package = "json5" }
Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -18,7 +18,7 @@

[JSON]: https://github.com/serde-rs/json
[TOML]: https://github.com/toml-lang/toml
[YAML]: https://github.com/chyh1990/yaml-rust
[YAML]: https://github.com/dtolnay/serde-yaml
[INI]: https://github.com/zonyitoo/rust-ini
[RON]: https://github.com/ron-rs/ron
[JSON5]: https://github.com/callum-oakley/json5-rs
Expand Down
2 changes: 1 addition & 1 deletion src/file/format/mod.rs
Expand Up @@ -40,7 +40,7 @@ pub enum FileFormat {
#[cfg(feature = "json")]
Json,

/// YAML (parsed with yaml_rust)
/// YAML (parsed with serde_yaml)
#[cfg(feature = "yaml")]
Yaml,

Expand Down
121 changes: 38 additions & 83 deletions src/file/format/yaml.rs
@@ -1,8 +1,4 @@
use std::error::Error;
use std::fmt;
use std::mem;

use yaml_rust as yaml;

use crate::format;
use crate::map::Map;
Expand All @@ -12,94 +8,53 @@ pub fn parse(
uri: Option<&String>,
text: &str,
) -> Result<Map<String, Value>, Box<dyn Error + Send + Sync>> {
// Parse a YAML object from file
let mut docs = yaml::YamlLoader::load_from_str(text)?;
let root = match docs.len() {
0 => yaml::Yaml::Hash(yaml::yaml::Hash::new()),
1 => mem::replace(&mut docs[0], yaml::Yaml::Null),
n => {
return Err(Box::new(MultipleDocumentsError(n)));
}
};

let value = from_yaml_value(uri, &root)?;
// Parse a YAML input from the provided text
let value = from_yaml_value(uri, serde_yaml::from_str(text)?);
format::extract_root_table(uri, value)
}

fn from_yaml_value(
uri: Option<&String>,
value: &yaml::Yaml,
) -> Result<Value, Box<dyn Error + Send + Sync>> {
match *value {
yaml::Yaml::String(ref value) => Ok(Value::new(uri, ValueKind::String(value.clone()))),
yaml::Yaml::Real(ref value) => {
// TODO: Figure out in what cases this can panic?
value
.parse::<f64>()
.map_err(|_| {
Box::new(FloatParsingError(value.to_string())) as Box<(dyn Error + Send + Sync)>
})
.map(ValueKind::Float)
.map(|f| Value::new(uri, f))
}
yaml::Yaml::Integer(value) => Ok(Value::new(uri, ValueKind::I64(value))),
yaml::Yaml::Boolean(value) => Ok(Value::new(uri, ValueKind::Boolean(value))),
yaml::Yaml::Hash(ref table) => {
let mut m = Map::new();
for (key, value) in table {
match key {
yaml::Yaml::String(k) => m.insert(k.to_owned(), from_yaml_value(uri, value)?),
yaml::Yaml::Integer(k) => m.insert(k.to_string(), from_yaml_value(uri, value)?),
_ => unreachable!(),
};
pub fn from_yaml_value(uri: Option<&String>, value: serde_yaml::Value) -> Value {
let vk = match value {
serde_yaml::Value::Tagged(_) | serde_yaml::Value::Null => ValueKind::Nil,
serde_yaml::Value::Bool(v) => ValueKind::Boolean(v),
serde_yaml::Value::String(v) => ValueKind::String(v),

serde_yaml::Value::Number(v) => {
if v.is_i64() {
ValueKind::I64(v.as_i64().expect("i64"))
} else if v.is_u64() {
ValueKind::U64(v.as_u64().expect("u64"))
} else if v.is_f64() {
ValueKind::Float(v.as_f64().expect("f64"))
} else {
ValueKind::Nil
}
Ok(Value::new(uri, ValueKind::Table(m)))
}
yaml::Yaml::Array(ref array) => {
let mut l = Vec::new();

for value in array {
l.push(from_yaml_value(uri, value)?);
}
serde_yaml::Value::Mapping(table) => {
let m = table
.into_iter()
.map(|(k, v)| {
let key = match k {
serde_yaml::Value::Number(v) => v.to_string(),
serde_yaml::Value::String(s) => s,

_ => unreachable!(),
};
let value = from_yaml_value(uri, v);
(key, value)
})
.collect();

Ok(Value::new(uri, ValueKind::Array(l)))
ValueKind::Table(m)
}

// 1. Yaml NULL
// 2. BadValue – It shouldn't be possible to hit BadValue as this only happens when
// using the index trait badly or on a type error but we send back nil.
// 3. Alias – No idea what to do with this and there is a note in the lib that its
// not fully supported yet anyway
_ => Ok(Value::new(uri, ValueKind::Nil)),
}
}
serde_yaml::Value::Sequence(array) => {
let l = array.into_iter().map(|v| from_yaml_value(uri, v)).collect();

#[derive(Debug, Copy, Clone)]
struct MultipleDocumentsError(usize);

impl fmt::Display for MultipleDocumentsError {
fn fmt(&self, format: &mut fmt::Formatter) -> fmt::Result {
write!(format, "Got {} YAML documents, expected 1", self.0)
}
}

impl Error for MultipleDocumentsError {
fn description(&self) -> &str {
"More than one YAML document provided"
}
}

#[derive(Debug, Clone)]
struct FloatParsingError(String);

impl fmt::Display for FloatParsingError {
fn fmt(&self, format: &mut fmt::Formatter) -> fmt::Result {
write!(format, "Parsing {} as floating point number failed", self.0)
}
}
ValueKind::Array(l)
}
};

impl Error for FloatParsingError {
fn description(&self) -> &str {
"Floating point number parsing failed"
}
Value::new(uri, vk)
}
5 changes: 3 additions & 2 deletions tests/file_yaml.rs
Expand Up @@ -81,12 +81,13 @@ fn test_error_parse() {

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

// Should fail to parse block mapping as no `:` exists to identify a key
assert!(res.is_err());
assert_eq!(
res.unwrap_err().to_string(),
format!(
"while parsing a block mapping, did not find expected key at \
line 2 column 1 in {}",
"could not find expected ':' at line 3 column 1, \
while scanning a simple key at line 2 column 1 in {}",
path_with_extension.display()
)
);
Expand Down
5 changes: 3 additions & 2 deletions tests/legacy/file_yaml.rs
Expand Up @@ -81,12 +81,13 @@ fn test_error_parse() {

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

// Should fail to parse block mapping as no `:` exists to identify a key
assert!(res.is_err());
assert_eq!(
res.unwrap_err().to_string(),
format!(
"while parsing a block mapping, did not find expected key at \
line 2 column 1 in {}",
"could not find expected ':' at line 3 column 1, \
while scanning a simple key at line 2 column 1 in {}",
path_with_extension.display()
)
);
Expand Down

0 comments on commit b815062

Please sign in to comment.