Skip to content

Commit 606a1ec

Browse files
committed
Compliance testing framework
1 parent d99ab31 commit 606a1ec

File tree

5 files changed

+189
-8
lines changed

5 files changed

+189
-8
lines changed

Cargo.toml

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
[package]
2-
name = "jsonpath-ri"
2+
name = "jsonpath_ri"
33
version = "0.0.1"
44
authors = ["Glyn Normington <[email protected]>"]
55
edition = "2018"
66

77
[dependencies]
8+
itertools = "0.9.0"
9+
json = "0.12.4"
10+
serde = { version = "1.0", features = ["derive"] }
11+
serde_json = "1.0.57"
12+
serde_yaml = "0.8.13"
13+
14+
[dependencies.yaml-rust]
15+
git = "https://github.com/chyh1990/yaml-rust.git"

src/jsonpath.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* Copyright 2020 VMware, Inc.
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
use serde_json::Value;
8+
9+
#[derive(Debug)]
10+
pub enum SyntaxError {
11+
Message(String)
12+
}
13+
14+
fn err(message: &str) -> Result<&dyn Path, SyntaxError> {
15+
Err(SyntaxError::Message(message.to_string()))
16+
}
17+
18+
pub fn parse(_selector: &str) -> Result<&dyn Path, SyntaxError> {
19+
err("not implemented")
20+
}
21+
22+
pub enum FindError {
23+
// no errors yet
24+
}
25+
26+
pub trait Path {
27+
fn find(&self, document: Value) -> Result<Vec<Value>, FindError>;
28+
}

src/lib.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
#[cfg(test)]
2-
mod tests {
3-
#[test]
4-
fn it_works() {
5-
assert_eq!(2 + 2, 4);
6-
}
7-
}
1+
/*
2+
* Copyright 2020 VMware, Inc.
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
pub mod jsonpath;

tests/cts.rs

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/*
2+
* Copyright 2020 VMware, Inc.
3+
*
4+
* SPDX-License-Identifier: BSD-2-Clause
5+
*/
6+
7+
#[cfg(test)]
8+
mod tests {
9+
use serde::{Deserialize, Serialize};
10+
use std::fs;
11+
use std::panic;
12+
use jsonpath_ri::jsonpath;
13+
14+
#[derive(Debug, PartialEq, Serialize, Deserialize)]
15+
struct TestSuite {
16+
tests: Vec<Testcase>,
17+
}
18+
19+
#[derive(Debug, PartialEq, Serialize, Deserialize)]
20+
struct Testcase {
21+
name: String,
22+
selector: String,
23+
document: serde_yaml::Value, // JSON deserialised as YAML
24+
result: serde_yaml::Value, // JSON deserialised as YAML
25+
}
26+
27+
#[test]
28+
fn compliance_test_suite() {
29+
let y = fs::read_to_string("tests/cts.yaml").expect("failed to read cts.yaml");
30+
31+
let suite: TestSuite = serde_yaml::from_str(&y).expect("failed to deserialize cts.yaml");
32+
33+
let mut errors: Vec<String> = Vec::new();
34+
for t in suite.tests {
35+
let result = panic::catch_unwind(|| {
36+
println!(
37+
"name = {}, selector = {}, document = {}, result = {}",
38+
t.name,
39+
t.selector,
40+
as_json(&t.document).expect("invalid document"),
41+
as_json(&t.result).expect("invalid result")
42+
);
43+
let path = jsonpath::parse(&t.selector);
44+
assert!(path.is_ok(), "parse failed: {:?}", path.err().expect("should be an error"));
45+
46+
if let Ok(p) = path {
47+
if let Ok(result) = p.find(as_json_value(&t.document).expect("invalid document")) {
48+
if result != as_json_value_array(&t.result).expect("invalid result") {
49+
assert!(false, "incorrect result")
50+
}
51+
} else {
52+
assert!(false, "find failed") // should not happen
53+
}
54+
}
55+
});
56+
if let Err(err) = result {
57+
errors.push(format!("{:?}", err));
58+
}
59+
}
60+
assert!(errors.is_empty())
61+
}
62+
63+
fn as_json(v: &serde_yaml::Value) -> Result<String, String> {
64+
match v {
65+
serde_yaml::Value::Null => Ok("null".to_string()),
66+
67+
serde_yaml::Value::Bool(b) => Ok(b.to_string()),
68+
69+
serde_yaml::Value::Number(num) => Ok(num.to_string()),
70+
71+
serde_yaml::Value::String(s) => Ok(json::stringify(s.to_string())),
72+
73+
serde_yaml::Value::Sequence(seq) => {
74+
let array_elements = seq
75+
.into_iter()
76+
.map(|v| as_json(v).expect("invalid sequence element"));
77+
Ok(format!("[{}]", itertools::join(array_elements, ",")))
78+
}
79+
80+
serde_yaml::Value::Mapping(map) => {
81+
let object_members = map.iter().map(|(k, v)| {
82+
format!(
83+
"{}:{}",
84+
as_json(k).expect("invalid object key"),
85+
as_json(v).expect("invalid object value")
86+
)
87+
});
88+
Ok(format!("{{{}}}", itertools::join(object_members, ",")))
89+
}
90+
}
91+
}
92+
93+
fn as_json_value(v: &serde_yaml::Value) -> Result<serde_json::Value, String> {
94+
match v {
95+
serde_yaml::Value::Null => Ok(serde_json::Value::Null),
96+
97+
serde_yaml::Value::Bool(b) => Ok(serde_json::Value::Bool(*b)),
98+
99+
serde_yaml::Value::Number(num) => Ok(serde_json::Value::Number(yaml_number_as_json(num.clone()))),
100+
101+
serde_yaml::Value::String(s) => Ok(serde_json::Value::String(s.clone())),
102+
103+
serde_yaml::Value::Sequence(seq) => {
104+
let array_elements = seq
105+
.into_iter()
106+
.map(|v| as_json_value(v).expect("invalid sequence element"));
107+
Ok(serde_json::Value::Array(array_elements.collect()))
108+
}
109+
110+
serde_yaml::Value::Mapping(map) => {
111+
let object_members = map.iter().map(|(k, v)| {
112+
(serde_yaml::to_string(k).expect("non-string mapping key"), as_json_value(v).expect("invalid map value"))
113+
});
114+
Ok(serde_json::Value::Object(object_members.collect()))
115+
}
116+
}
117+
}
118+
119+
fn as_json_value_array(v: &serde_yaml::Value) -> Result<Vec<serde_json::Value>, String> {
120+
match v {
121+
serde_yaml::Value::Sequence(seq) => {
122+
let array_elements = seq
123+
.into_iter()
124+
.map(|v| as_json_value(v).expect("invalid sequence element"));
125+
Ok(array_elements.collect())
126+
}
127+
_ => Err("not a sequence".to_string())
128+
}
129+
}
130+
131+
fn yaml_number_as_json(n: serde_yaml::Number) -> serde_json::Number {
132+
if n.is_i64() {
133+
serde_json::Number::from(n.as_i64().expect("invalid i64 in YAML"))
134+
} else if n.is_u64() {
135+
serde_json::Number::from(n.as_u64().expect("invalid u64 in YAML"))
136+
} else {
137+
serde_json::Number::from_f64(n.as_f64().expect("invalid f64 in YAML")).expect("invalid f64 for JSON")
138+
}
139+
}
140+
}

tests/cts.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
tests:
2+
- name: root
3+
selector: "$"
4+
document: ["first", "second"]
5+
result: [["first", "second"]]

0 commit comments

Comments
 (0)