use serde::{Deserialize, Serialize};
use std::{fs::File, io::Read, path::Path, result::Result as StdResult};
pub mod error;
pub mod v2;
pub mod v3;
pub use error::Error;
const MINIMUM_OPENAPI30_VERSION: &str = ">= 3.0";
pub type Result<T> = StdResult<T, Error>;
#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
#[serde(untagged)]
pub enum OpenApi {
V2(Box<v2::Spec>),
#[allow(non_camel_case_types)]
V3_0(Box<v3::Spec>),
}
pub fn from_path<P>(path: P) -> Result<OpenApi>
where
P: AsRef<Path>,
{
from_reader(File::open(path)?)
}
pub fn from_reader<R>(read: R) -> Result<OpenApi>
where
R: Read,
{
Ok(serde_yaml::from_reader::<R, OpenApi>(read)?)
}
pub fn to_yaml(spec: &OpenApi) -> Result<String> {
Ok(serde_yaml::to_string(spec)?)
}
pub fn to_json(spec: &OpenApi) -> Result<String> {
Ok(serde_json::to_string_pretty(spec)?)
}
#[cfg(test)]
mod tests {
use super::*;
use pretty_assertions::assert_eq;
use std::{
fs::{self, read_to_string, File},
io::Write,
};
fn write_to_file<P>(path: P, filename: &str, data: &str)
where
P: AsRef<Path> + std::fmt::Debug,
{
println!(" Saving string to {:?}...", path);
std::fs::create_dir_all(&path).unwrap();
let full_filename = path.as_ref().to_path_buf().join(filename);
let mut f = File::create(&full_filename).unwrap();
f.write_all(data.as_bytes()).unwrap();
}
fn convert_yaml_str_to_json(yaml_str: &str) -> String {
let yaml: serde_yaml::Value = serde_yaml::from_str(yaml_str).unwrap();
let json: serde_json::Value = serde_yaml::from_value(yaml).unwrap();
serde_json::to_string_pretty(&json).unwrap()
}
fn compare_spec_through_json(
input_file: &Path,
save_path_base: &Path,
) -> (String, String, String) {
let spec_yaml_str = read_to_string(&input_file)
.unwrap_or_else(|e| panic!("failed to read contents of {:?}: {}", input_file, e));
let spec_json_str = convert_yaml_str_to_json(&spec_yaml_str);
let parsed_spec = from_path(&input_file).unwrap();
let parsed_spec_json = serde_json::to_value(parsed_spec).unwrap();
let parsed_spec_json_str: String = serde_json::to_string_pretty(&parsed_spec_json).unwrap();
let api_filename = input_file
.file_name()
.unwrap()
.to_str()
.unwrap()
.replace(".yaml", ".json");
let mut save_path = save_path_base.to_path_buf();
save_path.push("yaml_to_json");
write_to_file(&save_path, &api_filename, &spec_json_str);
let mut save_path = save_path_base.to_path_buf();
save_path.push("yaml_to_spec_to_json");
write_to_file(&save_path, &api_filename, &parsed_spec_json_str);
(api_filename, parsed_spec_json_str, spec_json_str)
}
#[test]
fn can_deserialize() {
for entry in fs::read_dir("data/v2").unwrap() {
let path = entry.unwrap().path();
println!("Testing if {:?} is deserializable", path);
from_path(path).unwrap();
}
}
#[test]
fn can_deserialize_and_reserialize_v2() {
let save_path_base: std::path::PathBuf =
["target", "tests", "can_deserialize_and_reserialize_v2"]
.iter()
.collect();
for entry in fs::read_dir("data/v2").unwrap() {
let path = entry.unwrap().path();
println!("Testing if {:?} is deserializable", path);
let (api_filename, parsed_spec_json_str, spec_json_str) =
compare_spec_through_json(&path, &save_path_base);
assert_eq!(
parsed_spec_json_str.lines().collect::<Vec<_>>(),
spec_json_str.lines().collect::<Vec<_>>(),
"contents did not match for api {}",
api_filename
);
}
}
#[test]
fn can_deserialize_and_reserialize_v3() {
let save_path_base: std::path::PathBuf =
["target", "tests", "can_deserialize_and_reserialize_v3"]
.iter()
.collect();
for entry in fs::read_dir("data/v3.0").unwrap() {
let entry = entry.unwrap();
let path = entry.path();
println!("Testing if {:?} is deserializable", path);
let (api_filename, parsed_spec_json_str, spec_json_str) =
compare_spec_through_json(&path, &save_path_base);
assert_eq!(
parsed_spec_json_str.lines().collect::<Vec<_>>(),
spec_json_str.lines().collect::<Vec<_>>(),
"contents did not match for api {}",
api_filename
);
}
}
#[test]
fn can_deserialize_one_of_v3() {
let openapi = from_path("data/v3.0/petstore-expanded.yaml").unwrap();
if let OpenApi::V3_0(spec) = openapi {
let components = spec.components.unwrap();
let schemas = components.schemas.unwrap();
let obj_or_ref = schemas.get("PetSpecies");
if let Some(v3::ObjectOrReference::Object(schema)) = obj_or_ref {
assert_eq!(schema.one_of.as_ref().unwrap().len(), 2);
} else {
panic!("object should have been schema");
}
}
}
#[test]
fn can_deserialize_prtg() {
let openapi = from_path("data/v3.0/PRTG_openapi.yaml").unwrap();
if let OpenApi::V3_0(spec) = openapi {
let components = spec.components.unwrap();
let schemas = components.schemas.unwrap();
let obj_or_ref = schemas.get("prtg_status_warnings");
if obj_or_ref.is_none() {
panic!("object should have been an object");
}
}
}
}