1use serde::{Deserialize, Serialize};
33use std::{fs::File, io::Read, path::Path, result::Result as StdResult};
34
35pub mod error;
36pub mod v2;
37pub mod v3_0;
38
39pub use error::Error;
40
41const MINIMUM_OPENAPI30_VERSION: &str = ">= 3.0";
42
43pub type Result<T> = StdResult<T, Error>;
44
45#[derive(Clone, Debug, Deserialize, Serialize, PartialEq)]
47#[serde(untagged)]
48pub enum OpenApi {
49 V2(v2::Spec),
55
56 #[allow(non_camel_case_types)]
62 V3_0(v3_0::Spec),
63}
64
65pub fn from_path<P>(path: P) -> Result<OpenApi>
67where
68 P: AsRef<Path>,
69{
70 from_reader(File::open(path)?)
71}
72
73pub fn from_reader<R>(read: R) -> Result<OpenApi>
75where
76 R: Read,
77{
78 Ok(serde_yaml::from_reader::<R, OpenApi>(read)?)
79}
80
81pub fn to_yaml(spec: &OpenApi) -> Result<String> {
83 Ok(serde_yaml::to_string(spec)?)
84}
85
86pub fn to_json(spec: &OpenApi) -> Result<String> {
88 Ok(serde_json::to_string_pretty(spec)?)
89}
90
91#[cfg(test)]
92mod tests {
93 use super::*;
94 use pretty_assertions::assert_eq;
95 use std::{
96 fs::{self, read_to_string, File},
97 io::Write,
98 };
99
100 fn write_to_file<P>(
102 path: P,
103 filename: &str,
104 data: &str,
105 ) where
106 P: AsRef<Path> + std::fmt::Debug,
107 {
108 println!(" Saving string to {:?}...", path);
109 std::fs::create_dir_all(&path).unwrap();
110 let full_filename = path.as_ref().to_path_buf().join(filename);
111 let mut f = File::create(&full_filename).unwrap();
112 f.write_all(data.as_bytes()).unwrap();
113 }
114
115 fn convert_yaml_str_to_json(yaml_str: &str) -> String {
117 let yaml: serde_yaml::Value = serde_yaml::from_str(yaml_str).unwrap();
118 let json: serde_json::Value = serde_yaml::from_value(yaml).unwrap();
119 serde_json::to_string_pretty(&json).unwrap()
120 }
121
122 fn compare_spec_through_json(
134 input_file: &Path,
135 save_path_base: &Path,
136 ) -> (String, String, String) {
137 let spec_yaml_str = read_to_string(&input_file)
142 .unwrap_or_else(|e| panic!("failed to read contents of {:?}: {}", input_file, e));
143 let spec_json_str = convert_yaml_str_to_json(&spec_yaml_str);
145
146 let parsed_spec = from_path(&input_file).unwrap();
151 let parsed_spec_json = serde_json::to_value(parsed_spec).unwrap();
153 let parsed_spec_json_str: String = serde_json::to_string_pretty(&parsed_spec_json).unwrap();
155
156 let api_filename = input_file
158 .file_name()
159 .unwrap()
160 .to_str()
161 .unwrap()
162 .replace(".yaml", ".json");
163
164 let mut save_path = save_path_base.to_path_buf();
165 save_path.push("yaml_to_json");
166 write_to_file(&save_path, &api_filename, &spec_json_str);
167
168 let mut save_path = save_path_base.to_path_buf();
169 save_path.push("yaml_to_spec_to_json");
170 write_to_file(&save_path, &api_filename, &parsed_spec_json_str);
171
172 (api_filename, parsed_spec_json_str, spec_json_str)
174 }
175
176 #[test]
178 fn can_deserialize() {
179 for entry in fs::read_dir("data/v2").unwrap() {
180 let path = entry.unwrap().path();
181 println!("Testing if {:?} is deserializable", path);
183 from_path(path).unwrap();
184 }
185 }
186
187 #[test]
188 fn can_deserialize_and_reserialize_v2() {
189 let save_path_base: std::path::PathBuf =
190 ["target", "tests", "can_deserialize_and_reserialize_v2"]
191 .iter()
192 .collect();
193
194 for entry in fs::read_dir("data/v2").unwrap() {
195 let path = entry.unwrap().path();
196
197 println!("Testing if {:?} is deserializable", path);
198
199 let (api_filename, parsed_spec_json_str, spec_json_str) =
200 compare_spec_through_json(&path, &save_path_base);
201
202 assert_eq!(
203 parsed_spec_json_str.lines().collect::<Vec<_>>(),
204 spec_json_str.lines().collect::<Vec<_>>(),
205 "contents did not match for api {}",
206 api_filename
207 );
208 }
209 }
210
211 #[test]
212 fn can_deserialize_and_reserialize_v3() {
213 let save_path_base: std::path::PathBuf =
214 ["target", "tests", "can_deserialize_and_reserialize_v3"]
215 .iter()
216 .collect();
217
218 for entry in fs::read_dir("data/v3.0").unwrap() {
219 let entry = entry.unwrap();
220 let path = entry.path();
221
222 println!("Testing if {:?} is deserializable", path);
223
224 let (api_filename, parsed_spec_json_str, spec_json_str) =
225 compare_spec_through_json(&path, &save_path_base);
226
227 assert_eq!(
228 parsed_spec_json_str.lines().collect::<Vec<_>>(),
229 spec_json_str.lines().collect::<Vec<_>>(),
230 "contents did not match for api {}",
231 api_filename
232 );
233 }
234 }
235}