Skip to content

Commit 29bdff3

Browse files
authored
Merge pull request #2711 from input-output-hk/jpraynaud/2700-replace-serde-yml-dependency
refactor: replace unmaintained `serde_yml` dependency
2 parents 22f438c + 068bddd commit 29bdff3

File tree

8 files changed

+220
-39
lines changed

8 files changed

+220
-39
lines changed

Cargo.lock

Lines changed: 36 additions & 29 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-build-script"
3-
version = "0.2.26"
3+
version = "0.2.27"
44
description = "A toolbox for Mithril crates build scripts"
55
authors = { workspace = true }
66
edition = { workspace = true }
@@ -10,6 +10,6 @@ repository = { workspace = true }
1010
include = ["**/*.rs", "Cargo.toml", "README.md", ".gitignore"]
1111

1212
[dependencies]
13+
saphyr = "0.0.6"
1314
semver = { workspace = true }
1415
serde_json = { workspace = true }
15-
serde_yml = "0.0.12"

internal/mithril-build-script/src/open_api.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1-
use semver::Version;
21
use std::collections::BTreeMap;
32
use std::fs;
43
use std::path::{Path, PathBuf};
54

5+
use saphyr::{LoadableYamlNode, Yaml};
6+
use semver::Version;
7+
68
type OpenAPIFileName = String;
79
type OpenAPIVersionRaw = String;
810

@@ -29,7 +31,7 @@ pub fn list_all_open_api_spec_files(paths: &[&Path]) -> Vec<PathBuf> {
2931

3032
fn read_version_from_open_api_spec_file<P: AsRef<Path>>(spec_file_path: P) -> OpenAPIVersionRaw {
3133
let yaml_spec = fs::read_to_string(spec_file_path).unwrap();
32-
let open_api: serde_yml::Value = serde_yml::from_str(&yaml_spec).unwrap();
34+
let open_api = &Yaml::load_from_str(&yaml_spec).unwrap()[0];
3335
open_api["info"]["version"].as_str().unwrap().to_owned()
3436
}
3537

internal/tests/mithril-api-spec/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "mithril-api-spec"
3-
version = "0.1.6"
3+
version = "0.1.7"
44
authors.workspace = true
55
documentation.workspace = true
66
edition.workspace = true
@@ -11,9 +11,9 @@ repository.workspace = true
1111
[dependencies]
1212
jsonschema = { version = "0.33.0" }
1313
reqwest = { workspace = true }
14+
saphyr = "0.0.6"
1415
serde = { workspace = true }
1516
serde_json = { workspace = true }
16-
serde_yml = "0.0.12"
1717
warp = { workspace = true }
1818

1919
[dev-dependencies]

internal/tests/mithril-api-spec/src/apispec.rs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ use jsonschema::Validator;
44
use reqwest::Url;
55
use serde::Serialize;
66
use serde_json::{Value, Value::Null, json};
7-
87
use warp::http::Response;
98
use warp::http::StatusCode;
109
use warp::hyper::body::Bytes;
1110

11+
use crate::spec_parser::OpenApiSpecParser;
12+
1213
#[cfg(test)]
1314
pub(crate) const DEFAULT_SPEC_FILE: &str = "../../../openapi.yaml";
1415

@@ -55,10 +56,8 @@ impl<'a> APISpec<'a> {
5556

5657
/// APISpec factory from spec
5758
pub fn from_file(path: &str) -> APISpec<'a> {
58-
let yaml_spec = std::fs::read_to_string(path).unwrap();
59-
let openapi: serde_json::Value = serde_yml::from_str(&yaml_spec).unwrap();
6059
APISpec {
61-
openapi,
60+
openapi: OpenApiSpecParser::parse_yaml(path).unwrap(),
6261
path: None,
6362
method: None,
6463
content_type: Some("application/json"),

internal/tests/mithril-api-spec/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
//! This crate provides a toolset to verify conformity of http routes against an Open Api specification.
33
44
mod apispec;
5+
pub(crate) mod spec_parser;
56

67
pub use apispec::*;
78

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
mod yaml;
2+
3+
/// Parser for OpenApi Specification
4+
///
5+
/// Returns the parsed spec as a [serde_json::Value] as this crate uses a jsonschema validator
6+
/// to do the validation.
7+
pub(crate) struct OpenApiSpecParser;
8+
9+
impl OpenApiSpecParser {
10+
pub(crate) fn parse_yaml(spec_path: &str) -> Result<serde_json::Value, String> {
11+
use saphyr::LoadableYamlNode;
12+
13+
let yaml_spec = std::fs::read_to_string(spec_path)
14+
.map_err(|e| format!("Could not read spec file `{spec_path}`: {e}"))?;
15+
let openapi = saphyr::Yaml::load_from_str(&yaml_spec)
16+
.map_err(|e| format!("Could not parse spec file `{spec_path}`: {e}"))?;
17+
18+
if openapi.is_empty() {
19+
Err("No spec found in file".to_string())
20+
} else {
21+
yaml::convert_yaml_to_serde_json(&openapi[0])
22+
}
23+
}
24+
}
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
use saphyr::{Scalar, Yaml};
2+
use serde_json::Value;
3+
4+
/// Converts a [saphyr::Yaml][Yaml] value to a [serde_json::Value][Value].
5+
///
6+
/// This is a crude, minimal, implementation aimed only at handling `mithril-api-spec` needs of
7+
/// parsing `openapi.yaml` files.
8+
/// It should not be reused as-is for other cases.
9+
pub(crate) fn convert_yaml_to_serde_json(yaml_value: &Yaml) -> Result<Value, String> {
10+
match yaml_value {
11+
Yaml::Value(scalar) => match scalar {
12+
Scalar::String(s) => Ok(Value::String(s.to_string())),
13+
Scalar::Integer(i) => Ok(Value::from(*i)),
14+
Scalar::FloatingPoint(f) => {
15+
serde_json::Number::from_f64(**f).map(Value::Number).ok_or_else(|| {
16+
format!(
17+
"Could not convert saphyr::Yaml::FloatingPoint {} to JSON number",
18+
f
19+
)
20+
})
21+
}
22+
Scalar::Boolean(b) => Ok(Value::Bool(*b)),
23+
Scalar::Null => Ok(Value::Null),
24+
},
25+
Yaml::Sequence(seq) => {
26+
let mut json_array = Vec::new();
27+
for item in seq {
28+
json_array.push(convert_yaml_to_serde_json(item)?);
29+
}
30+
Ok(Value::Array(json_array))
31+
}
32+
Yaml::Mapping(map) => {
33+
let mut json_obj = serde_json::Map::new();
34+
for (key, value) in map {
35+
let key_str = match key {
36+
Yaml::Value(Scalar::String(s)) => s,
37+
_ => return Err("saphyr::Yaml::Mapping key must be a string".to_string()),
38+
};
39+
json_obj.insert(key_str.to_string(), convert_yaml_to_serde_json(value)?);
40+
}
41+
Ok(Value::Object(json_obj))
42+
}
43+
Yaml::Representation(_val, _scalar_style, _tag) => {
44+
Err("saphyr::Yaml::Representation variant is not supported".to_string())
45+
}
46+
Yaml::Tagged(_, boxed) => convert_yaml_to_serde_json(boxed),
47+
Yaml::Alias(_) => Err("saphyr::Yaml::Alias are not supported".to_string()),
48+
Yaml::BadValue => Err("Bad YAML value".to_string()),
49+
}
50+
}
51+
52+
#[cfg(test)]
53+
mod tests {
54+
use saphyr::LoadableYamlNode;
55+
56+
use super::*;
57+
58+
#[test]
59+
fn test_convert_scalar_string() {
60+
let yaml = Yaml::Value(Scalar::String("test".into()));
61+
let json = convert_yaml_to_serde_json(&yaml).unwrap();
62+
assert_eq!(json, Value::String("test".to_string()));
63+
}
64+
65+
#[test]
66+
fn test_convert_scalar_int() {
67+
let yaml = Yaml::Value(Scalar::Integer(42));
68+
let json = convert_yaml_to_serde_json(&yaml).unwrap();
69+
assert_eq!(json, Value::Number(42.into()));
70+
}
71+
72+
#[test]
73+
fn test_convert_scalar_float() {
74+
let yaml = Yaml::load_from_str("2.14").unwrap()[0].to_owned();
75+
let json = convert_yaml_to_serde_json(&yaml).unwrap();
76+
assert!(json.is_number());
77+
assert_eq!(json.as_f64().unwrap(), 2.14);
78+
}
79+
80+
#[test]
81+
fn test_convert_scalar_bool() {
82+
let yaml = Yaml::Value(Scalar::Boolean(true));
83+
let json = convert_yaml_to_serde_json(&yaml).unwrap();
84+
assert_eq!(json, Value::Bool(true));
85+
}
86+
87+
#[test]
88+
fn test_convert_scalar_null() {
89+
let yaml = Yaml::Value(Scalar::Null);
90+
let json = convert_yaml_to_serde_json(&yaml).unwrap();
91+
assert_eq!(json, Value::Null);
92+
}
93+
94+
#[test]
95+
fn test_convert_sequence() {
96+
let yaml = Yaml::Sequence(vec![
97+
Yaml::Value(Scalar::Integer(1)),
98+
Yaml::Value(Scalar::Integer(2)),
99+
]);
100+
let json = convert_yaml_to_serde_json(&yaml).unwrap();
101+
assert_eq!(
102+
json,
103+
Value::Array(vec![Value::Number(1.into()), Value::Number(2.into())])
104+
);
105+
}
106+
107+
#[test]
108+
fn test_convert_mapping() {
109+
let yaml = Yaml::load_from_str("key: value").unwrap()[0].to_owned();
110+
let json = convert_yaml_to_serde_json(&yaml).unwrap();
111+
let mut expected = serde_json::Map::new();
112+
expected.insert("key".to_string(), Value::String("value".to_string()));
113+
assert_eq!(json, Value::Object(expected));
114+
}
115+
116+
#[test]
117+
fn test_invalid_mapping_key() {
118+
let yaml = Yaml::load_from_str("42: value").unwrap()[0].to_owned();
119+
assert_eq!(
120+
convert_yaml_to_serde_json(&yaml).unwrap_err(),
121+
"saphyr::Yaml::Mapping key must be a string"
122+
);
123+
}
124+
125+
#[test]
126+
fn test_representation_is_not_supported() {
127+
let yaml = Yaml::Representation("test".into(), saphyr::ScalarStyle::SingleQuoted, None);
128+
let error = convert_yaml_to_serde_json(&yaml).unwrap_err();
129+
assert_eq!(
130+
error,
131+
"saphyr::Yaml::Representation variant is not supported"
132+
);
133+
}
134+
135+
#[test]
136+
fn test_alias_is_not_supported() {
137+
let yaml = Yaml::Alias(1);
138+
let error = convert_yaml_to_serde_json(&yaml).unwrap_err();
139+
assert_eq!(error, "saphyr::Yaml::Alias are not supported");
140+
}
141+
142+
#[test]
143+
fn test_bad_value_is_not_supported() {
144+
let yaml = Yaml::BadValue;
145+
let error = convert_yaml_to_serde_json(&yaml).unwrap_err();
146+
assert_eq!(error, "Bad YAML value");
147+
}
148+
}

0 commit comments

Comments
 (0)