Skip to content

Commit

Permalink
Merge pull request #2240 from input-output-hk/sfa/2235/openapi_exampl…
Browse files Browse the repository at this point in the history
…es_check

Openapi examples check
  • Loading branch information
sfauvel authored Jan 23, 2025
2 parents e1de875 + 829e4e4 commit d1dc173
Show file tree
Hide file tree
Showing 5 changed files with 821 additions and 653 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/website/docusaurus.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ const config = {
{
specs: [
{
spec: "../openapi.yaml",
spec: "../../openapi.yaml",
route: "/aggregator-api/",
},
],
Expand Down
2 changes: 1 addition & 1 deletion mithril-common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "mithril-common"
version = "0.4.107"
version = "0.4.108"
description = "Common types, interfaces, and utilities for Mithril nodes."
authors = { workspace = true }
edition = { workspace = true }
Expand Down
209 changes: 178 additions & 31 deletions mithril-common/src/test_utils/apispec.rs
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,11 @@ impl<'a> APISpec<'a> {
let components = self.openapi["components"].clone();
schema.insert(String::from("components"), components);

let validator = Validator::new(&json!(schema)).unwrap();
let result_validator = Validator::new(&json!(schema));
if let Err(e) = result_validator {
return Err(format!("Error creating validator: {e}"));
}
let validator = result_validator.unwrap();
let errors = validator
.iter_errors(value)
.map(|e| e.to_string())
Expand Down Expand Up @@ -295,20 +299,43 @@ impl<'a> APISpec<'a> {
}

fn verify_example_conformity(&self, name: &str, component: &Value) -> Vec<String> {
fn register_example_errors(
apispec: &APISpec,
errors: &mut Vec<String>,
component_definition: &Value,
example: &Value,
) {
let result = apispec.validate_conformity(example, component_definition);
if let Err(e) = result {
errors.push(format!(" {}\n Example: {}\n", e, example));
}
}

let mut errors = vec![];
let component_definition = component.get("schema").unwrap_or(component);
// The type definition is at the same level as the example (components) unless there is a schema property (paths).
if let Some(example) = component.get("example") {
// The type definition is at the same level as the example (components) unless there is a schema property (paths).
let component_definition = component.get("schema").unwrap_or(component);
register_example_errors(self, &mut errors, component_definition, example);
}

let result = self.validate_conformity(example, component_definition);
if let Err(e) = result {
return vec![format!(
"- {}: Error\n {}\n Example: {}\n",
name, e, example
)];
if let Some(examples) = component.get("examples") {
if let Some(examples) = examples.as_array() {
for example in examples {
register_example_errors(self, &mut errors, component_definition, example);
}
} else {
errors.push(format!(
" Examples should be an array\n Examples: {}\n",
examples
));
}
}

vec![]
if !errors.is_empty() {
vec![format!("- {}: Error\n{}", name, errors.join("\n"))]
} else {
vec![]
}
}
}

Expand Down Expand Up @@ -402,11 +429,11 @@ components:
/// we create an openapi.yaml with an invalid example.
/// If the example is verified, we should have an error message.
/// A simple invalid example is one with a wrong type (string instead of integer)
fn check_example_error_is_detected(
fn check_example_errors_is_detected(
id: u32,
paths: &str,
components: &str,
expected_error_message: &str,
expected_error_messages: &[&str],
) {
let file = get_temp_openapi_filename("example", id);

Expand All @@ -417,12 +444,14 @@ components:

assert_eq!(1, errors.len());
let error_message = errors.first().unwrap();
assert!(
error_message.contains(expected_error_message),
"Error message: {:?}\nshould contains: {}\n",
errors,
expected_error_message
);
for expected_message in expected_error_messages {
assert!(
error_message.contains(expected_message),
"Error message: {:?}\nshould contains: {}\n",
errors,
expected_message
);
}
}

#[test]
Expand Down Expand Up @@ -786,6 +815,104 @@ components:
assert!(spec_files.contains(&APISpec::get_default_spec_file()))
}

fn check_example_detect_no_error(id: u32, paths: &str, components: &str) {
let file = get_temp_openapi_filename("example", id);

write_minimal_open_api_file("1.0.0", &file, paths, components);

let api_spec = APISpec::from_file(file.to_str().unwrap());
let errors: Vec<String> = api_spec.verify_examples();

let error_messages = errors.join("\n");
assert_eq!(0, errors.len(), "Error messages: {}", error_messages);
}

#[test]
fn test_example_success_with_a_valid_example() {
let components = r#"
MyComponent:
type: object
properties:
id:
type: integer
example:
{
"id": 123,
}
"#;
check_example_detect_no_error(line!(), "", components);
}

#[test]
fn test_examples_success_with_a_valid_examples() {
let components = r#"
MyComponent:
type: object
properties:
id:
type: integer
examples:
- {
"id": 123
}
- {
"id": 456
}
"#;
check_example_detect_no_error(line!(), "", components);
}

#[test]
fn test_examples_is_verified_on_object() {
let components = r#"
MyComponent:
type: object
properties:
id:
type: integer
examples:
- {
"id": 123
}
- {
"id": "abc"
}
- {
"id": "def"
}
"#;
check_example_errors_is_detected(
line!(),
"",
components,
&[
"\"abc\" is not of type \"integer\"",
"\"def\" is not of type \"integer\"",
],
);
}

#[test]
fn test_examples_should_be_an_array() {
let components = r#"
MyComponent:
type: object
properties:
id:
type: integer
examples:
{
"id": 123
}
"#;
check_example_errors_is_detected(
line!(),
"",
components,
&["Examples should be an array", "Examples: {\"id\":123}"],
);
}

#[test]
fn test_example_is_verified_on_object() {
let components = r#"
Expand All @@ -799,11 +926,11 @@ components:
"id": "abc",
}
"#;
check_example_error_is_detected(
check_example_errors_is_detected(
line!(),
"",
components,
"\"abc\" is not of type \"integer\"",
&["\"abc\" is not of type \"integer\""],
);
}

Expand All @@ -819,11 +946,11 @@ components:
"abc"
]
"#;
check_example_error_is_detected(
check_example_errors_is_detected(
line!(),
"",
components,
"\"abc\" is not of type \"integer\"",
&["\"abc\" is not of type \"integer\""],
);
}

Expand All @@ -837,11 +964,11 @@ components:
example:
"abc"
"#;
check_example_error_is_detected(
check_example_errors_is_detected(
line!(),
"",
components,
"\"abc\" is not of type \"integer\"",
&["\"abc\" is not of type \"integer\""],
);
}

Expand All @@ -855,9 +982,14 @@ components:
in: path
schema:
type: integer
example: "abc"
example: "abc"
"#;
check_example_error_is_detected(line!(), paths, "", "\"abc\" is not of type \"integer\"");
check_example_errors_is_detected(
line!(),
paths,
"",
&["\"abc\" is not of type \"integer\""],
);
}

#[test]
Expand All @@ -877,7 +1009,12 @@ components:
"abc"
]
"#;
check_example_error_is_detected(line!(), paths, "", "\"abc\" is not of type \"integer\"");
check_example_errors_is_detected(
line!(),
paths,
"",
&["\"abc\" is not of type \"integer\""],
);
}

#[test]
Expand All @@ -897,7 +1034,12 @@ components:
"abc"
]
"#;
check_example_error_is_detected(line!(), paths, "", "\"abc\" is not of type \"integer\"");
check_example_errors_is_detected(
line!(),
paths,
"",
&["\"abc\" is not of type \"integer\""],
);
}

#[test]
Expand All @@ -915,7 +1057,12 @@ components:
example:
"abc"
"#;
check_example_error_is_detected(line!(), paths, "", "\"abc\" is not of type \"integer\"");
check_example_errors_is_detected(
line!(),
paths,
"",
&["\"abc\" is not of type \"integer\""],
);
}

#[test]
Expand All @@ -935,11 +1082,11 @@ components:
type: integer
"#;

check_example_error_is_detected(
check_example_errors_is_detected(
line!(),
paths,
components,
"\"abc\" is not of type \"integer\"",
&["\"abc\" is not of type \"integer\""],
);
}
}
Loading

0 comments on commit d1dc173

Please sign in to comment.