Skip to content

Openapi examples check #2240

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Jan 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading