1use serde::{Deserialize, Serialize};
28use std::{fs::File, io::Read, path::Path, result::Result as StdResult};
29
30pub mod error;
31pub mod v2;
32pub mod v3;
33
34pub use error::Error;
35
36const MINIMUM_OPENAPI30_VERSION: &str = ">= 3.0";
37
38pub type Result<T> = StdResult<T, Error>;
39
40#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
42#[serde(untagged)]
43pub enum OpenApi {
44 V2(Box<v2::Spec>),
50
51 #[allow(non_camel_case_types)]
57 V3_0(Box<v3::Spec>),
58}
59
60pub fn from_path<P>(path: P) -> Result<OpenApi>
62where
63 P: AsRef<Path>,
64{
65 from_reader(File::open(path)?)
66}
67
68pub fn from_reader<R>(read: R) -> Result<OpenApi>
70where
71 R: Read,
72{
73 Ok(serde_yaml::from_reader::<R, OpenApi>(read)?)
74}
75
76pub fn to_yaml(spec: &OpenApi) -> Result<String> {
78 Ok(serde_yaml::to_string(spec)?)
79}
80
81pub fn to_json(spec: &OpenApi) -> Result<String> {
83 Ok(serde_json::to_string_pretty(spec)?)
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89 use pretty_assertions::assert_eq;
90 use std::{
91 fs::{self, read_to_string, File},
92 io::Write,
93 };
94
95 fn write_to_file<P>(path: P, filename: &str, data: &str)
97 where
98 P: AsRef<Path> + std::fmt::Debug,
99 {
100 println!(" Saving string to {:?}...", path);
101 std::fs::create_dir_all(&path).unwrap();
102 let full_filename = path.as_ref().to_path_buf().join(filename);
103 let mut f = File::create(&full_filename).unwrap();
104 f.write_all(data.as_bytes()).unwrap();
105 }
106
107 fn convert_yaml_str_to_json(yaml_str: &str) -> String {
109 let yaml: serde_yaml::Value = serde_yaml::from_str(yaml_str).unwrap();
110 let json: serde_json::Value = serde_yaml::from_value(yaml).unwrap();
111 serde_json::to_string_pretty(&json).unwrap()
112 }
113
114 fn compare_spec_through_json(
126 input_file: &Path,
127 save_path_base: &Path,
128 ) -> (String, String, String) {
129 let spec_yaml_str = read_to_string(&input_file)
134 .unwrap_or_else(|e| panic!("failed to read contents of {:?}: {}", input_file, e));
135 let spec_json_str = convert_yaml_str_to_json(&spec_yaml_str);
137
138 let parsed_spec = from_path(&input_file).unwrap();
143 let parsed_spec_json = serde_json::to_value(parsed_spec).unwrap();
145 let parsed_spec_json_str: String = serde_json::to_string_pretty(&parsed_spec_json).unwrap();
147
148 let api_filename = input_file
150 .file_name()
151 .unwrap()
152 .to_str()
153 .unwrap()
154 .replace(".yaml", ".json");
155
156 let mut save_path = save_path_base.to_path_buf();
157 save_path.push("yaml_to_json");
158 write_to_file(&save_path, &api_filename, &spec_json_str);
159
160 let mut save_path = save_path_base.to_path_buf();
161 save_path.push("yaml_to_spec_to_json");
162 write_to_file(&save_path, &api_filename, &parsed_spec_json_str);
163
164 (api_filename, parsed_spec_json_str, spec_json_str)
166 }
167
168 #[test]
170 fn can_deserialize() {
171 for entry in fs::read_dir("data/v2").unwrap() {
172 let path = entry.unwrap().path();
173 println!("Testing if {:?} is deserializable", path);
175 from_path(path).unwrap();
176 }
177 }
178
179 #[test]
180 fn can_deserialize_and_reserialize_v2() {
181 let save_path_base: std::path::PathBuf =
182 ["target", "tests", "can_deserialize_and_reserialize_v2"]
183 .iter()
184 .collect();
185
186 for entry in fs::read_dir("data/v2").unwrap() {
187 let path = entry.unwrap().path();
188
189 println!("Testing if {:?} is deserializable", path);
190
191 let (api_filename, parsed_spec_json_str, spec_json_str) =
192 compare_spec_through_json(&path, &save_path_base);
193
194 assert_eq!(
195 parsed_spec_json_str.lines().collect::<Vec<_>>(),
196 spec_json_str.lines().collect::<Vec<_>>(),
197 "contents did not match for api {}",
198 api_filename
199 );
200 }
201 }
202
203 #[test]
204 fn can_deserialize_and_reserialize_v3() {
205 let save_path_base: std::path::PathBuf =
206 ["target", "tests", "can_deserialize_and_reserialize_v3"]
207 .iter()
208 .collect();
209
210 for entry in fs::read_dir("data/v3.0").unwrap() {
211 let entry = entry.unwrap();
212 let path = entry.path();
213
214 println!("Testing if {:?} is deserializable", path);
215
216 let (api_filename, parsed_spec_json_str, spec_json_str) =
217 compare_spec_through_json(&path, &save_path_base);
218
219 assert_eq!(
220 parsed_spec_json_str.lines().collect::<Vec<_>>(),
221 spec_json_str.lines().collect::<Vec<_>>(),
222 "contents did not match for api {}",
223 api_filename
224 );
225 }
226 }
227
228 #[test]
229 fn can_deserialize_one_of_v3() {
230 let openapi = from_path("data/v3.0/petstore-expanded.yaml").unwrap();
231 if let OpenApi::V3_0(spec) = openapi {
232 let components = spec.components.unwrap();
233 let schemas = components.schemas.unwrap();
234 let obj_or_ref = schemas.get("PetSpecies");
235
236 if let Some(v3::ObjectOrReference::Object(schema)) = obj_or_ref {
237 assert_eq!(schema.one_of.as_ref().unwrap().len(), 2);
239 } else {
240 panic!("object should have been schema");
241 }
242 }
243 }
244#[test]
260 fn can_deserialize_prtg() {
261 let openapi = from_path("data/v3.0/PRTG_openapi.yaml").unwrap();
263 if let OpenApi::V3_0(spec) = openapi {
264 let components = spec.components.unwrap();
265 let schemas = components.schemas.unwrap();
266 let obj_or_ref = schemas.get("prtg_status_warnings");
267
268 if obj_or_ref.is_none() {
269 panic!("object should have been an object");
270 }
271 }
272 }
273
274}