mithril_build_script/
open_api.rs1use std::collections::BTreeMap;
2use std::fs;
3use std::path::{Path, PathBuf};
4
5use saphyr::{LoadableYamlNode, Yaml};
6use semver::Version;
7
8type OpenAPIFileName = String;
9type OpenAPIVersionRaw = String;
10
11const TYPE_ALIAS: &str = r"/// Open API file name
12pub type OpenAPIFileName = String;
13";
14
15pub fn list_all_open_api_spec_files(paths: &[&Path]) -> Vec<PathBuf> {
17 let mut open_api_spec_files = Vec::new();
18
19 for path in paths {
20 for entry in crate::list_files_in_folder(path).filter(|e| {
21 let os_filename = e.file_name();
22 let filename = os_filename.to_string_lossy();
23 filename.starts_with("openapi") && filename.ends_with(".yaml")
24 }) {
25 open_api_spec_files.push(entry.path())
26 }
27 }
28
29 open_api_spec_files
30}
31
32fn read_version_from_open_api_spec_file<P: AsRef<Path>>(spec_file_path: P) -> OpenAPIVersionRaw {
33 let yaml_spec = fs::read_to_string(spec_file_path).unwrap();
34 let open_api = &Yaml::load_from_str(&yaml_spec).unwrap()[0];
35 open_api["info"]["version"].as_str().unwrap().to_owned()
36}
37
38pub fn generate_open_api_versions_mapping(open_api_spec_files: &[PathBuf]) -> String {
40 let open_api_versions: BTreeMap<OpenAPIFileName, Version> = open_api_spec_files
42 .iter()
43 .map(|path| (path.clone(), read_version_from_open_api_spec_file(path)))
44 .map(|(path, version_raw)| {
45 (
46 path.file_name().unwrap().to_string_lossy().to_string(),
47 Version::parse(&version_raw).unwrap(),
48 )
49 })
50 .collect();
51
52 let mut open_api_versions_hashmap = String::new();
53 for (filename, version) in open_api_versions {
54 open_api_versions_hashmap.push_str(&format!(
55 r#"("{filename}".to_string(), semver::Version::new({}, {}, {})), "#,
56 version.major, version.minor, version.patch
57 ));
58 }
59
60 format!(
61 r#"{TYPE_ALIAS}
62/// Build Open API versions mapping
63pub fn get_open_api_versions_mapping() -> HashMap<OpenAPIFileName, semver::Version> {{
64 HashMap::from([
65 {open_api_versions_hashmap}
66 ])
67}}
68 "#
69 )
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75 use crate::get_temp_dir;
76 use std::path::Path;
77
78 fn write_minimal_open_api_file(version: &str, path: &Path) {
79 fs::write(
80 path,
81 format!(
82 r#"openapi: "3.0.0"
83info:
84 version: {version}
85 title: Minimal Open Api File
86"#
87 ),
88 )
89 .unwrap()
90 }
91
92 fn assert_open_api_content_contains(expected_content: &str, generated_code: &str) {
93 assert!(
94 generated_code.contains(expected_content),
95 "generated code did not include expected openapi files entries:\
96 \n---- Code that was expected to be included:\n{expected_content}\
97 \n---- Actual generated code:{}",
98 generated_code.trim_start_matches(TYPE_ALIAS)
100 );
101 }
102
103 #[test]
104 fn generated_code_include_type_aliases() {
105 let open_api_spec_files = list_all_open_api_spec_files(&[Path::new("./")]);
106 let generated_code = generate_open_api_versions_mapping(&open_api_spec_files);
107
108 assert!(generated_code.contains(TYPE_ALIAS));
109 }
110
111 #[test]
112 fn generated_function_returns_an_hashmap_of_open_api_file_name_and_semver_version() {
113 let open_api_spec_files = list_all_open_api_spec_files(&[Path::new("./")]);
114 let generated_code = generate_open_api_versions_mapping(&open_api_spec_files);
115
116 assert!(generated_code.contains("-> HashMap<OpenAPIFileName, semver::Version>"));
117 }
118
119 #[test]
120 fn generate_code_from_a_simple_open_api_file() {
121 let dir = get_temp_dir("generate_code_from_a_simple_open_api_file");
122 write_minimal_open_api_file("1.0.0", &dir.join("openapi.yaml"));
123
124 let expected = r#"("openapi.yaml".to_string(), semver::Version::new(1, 0, 0))"#;
125 let open_api_spec_files = list_all_open_api_spec_files(&[&dir]);
126 let generated_code = generate_open_api_versions_mapping(&open_api_spec_files);
127
128 assert_open_api_content_contains(expected, &generated_code);
129 }
130
131 #[test]
132 fn only_read_yaml_files() {
133 let dir = get_temp_dir("only_read_yaml_files");
134 write_minimal_open_api_file("1.0.0", &dir.join("openapi.yaml"));
135 fs::write(dir.join("openapi.json"), "{}").unwrap();
136
137 let included_files = list_all_open_api_spec_files(&[&dir]);
138
139 assert_eq!(vec![dir.join("openapi.yaml")], included_files);
140 }
141
142 #[test]
143 fn generate_code_from_two_open_api_files_in_different_folders() {
144 let sub_folder =
145 get_temp_dir("generate_code_from_two_open_api_files_in_different_folders/subfolder");
146 let parent_folder = sub_folder.parent().unwrap();
147 write_minimal_open_api_file("1.0.0", &parent_folder.join("openapi.yaml"));
148 write_minimal_open_api_file("2.0.0", &sub_folder.join("openapi-thales.yaml"));
149
150 let expected = r#"("openapi-thales.yaml".to_string(), semver::Version::new(2, 0, 0)), ("openapi.yaml".to_string(), semver::Version::new(1, 0, 0))"#;
151 let open_api_spec_files = list_all_open_api_spec_files(&[parent_folder, &sub_folder]);
152 let generated_code = generate_open_api_versions_mapping(&open_api_spec_files);
153
154 assert_open_api_content_contains(expected, &generated_code);
155 }
156
157 #[test]
158 fn when_colliding_filenames_version_is_read_from_latest_given_folder() {
159 let sub_folder = get_temp_dir(
160 "when_colliding_filenames_version_read_is_from_latest_given_folder/subfolder",
161 );
162 let parent_folder = sub_folder.parent().unwrap();
163 write_minimal_open_api_file("1.0.0", &parent_folder.join("openapi.yaml"));
164 write_minimal_open_api_file("2.0.0", &sub_folder.join("openapi.yaml"));
165
166 let expected = r#"HashMap::from([
167 ("openapi.yaml".to_string(), semver::Version::new(2, 0, 0)),
168 ])"#;
169 let open_api_spec_files = list_all_open_api_spec_files(&[parent_folder, &sub_folder]);
170 let generated_code = generate_open_api_versions_mapping(&open_api_spec_files);
171
172 assert_open_api_content_contains(expected, &generated_code);
173 }
174}