golem_openapi_client_generator/
lib.rs1use std::collections::HashMap;
16use std::fs::File;
17use std::io::BufReader;
18use std::path::{Path, PathBuf};
19use std::result;
20
21use openapiv3::OpenAPI;
22
23pub use merger::merge_all_openapi_specs;
24
25use crate::rust::lib_gen::{Module, ModuleDef, ModuleName};
26use crate::rust::model_gen::RefCache;
27
28pub(crate) mod merger;
29pub(crate) mod printer;
30mod rust;
31mod toml;
32
33#[derive(Debug, Clone)]
34pub enum Error {
35 Unexpected { message: String },
36 Unimplemented { message: String },
37}
38
39impl Error {
40 pub fn unexpected<S: Into<String>>(message: S) -> Error {
41 Error::Unexpected {
42 message: message.into(),
43 }
44 }
45 pub fn unimplemented<S: Into<String>>(message: S) -> Error {
46 Error::Unimplemented {
47 message: message.into(),
48 }
49 }
50
51 pub fn extend<S: Into<String>>(&self, s: S) -> Error {
52 match self {
53 Error::Unexpected { message } => Error::Unexpected {
54 message: format!("{} {}", s.into(), message.clone()),
55 },
56 Error::Unimplemented { message } => Error::Unimplemented {
57 message: format!("{} {}", s.into(), message.clone()),
58 },
59 }
60 }
61}
62
63pub type Result<T> = result::Result<T, Error>;
64
65#[allow(clippy::too_many_arguments)]
66pub fn gen(
67 openapi_specs: Vec<OpenAPI>,
68 target: &Path,
69 name: &str,
70 version: &str,
71 overwrite_cargo: bool,
72 disable_clippy: bool,
73 mapping: &[(&str, &str)],
74 ignored_paths: &[&str],
75) -> Result<()> {
76 let mapping: HashMap<&str, &str> = HashMap::from_iter(mapping.iter().cloned());
77
78 let open_api = merge_all_openapi_specs(openapi_specs)?;
79
80 let src = target.join("src");
81 let api = src.join("api");
82 let model = src.join("model");
83
84 std::fs::create_dir_all(&api).unwrap();
85 std::fs::create_dir_all(&model).unwrap();
86
87 let context = rust::context_gen::context_gen(&open_api)?;
88 std::fs::write(src.join(context.def.name.file_name()), &context.code).unwrap();
89
90 let mut ref_cache = RefCache::new();
91
92 let modules: Result<Vec<Module>> = open_api
93 .tags
94 .iter()
95 .map(|tag| {
96 rust::client_gen::client_gen(
97 &open_api,
98 Some(tag.clone()),
99 &mut ref_cache,
100 ignored_paths,
101 )
102 })
103 .collect();
104
105 let mut api_module_defs = Vec::new();
106
107 for module in modules? {
108 std::fs::write(api.join(module.def.name.file_name()), module.code).unwrap();
109 api_module_defs.push(module.def.clone())
110 }
111
112 let mut known_refs = RefCache::new();
113 let mut models = Vec::new();
114
115 let multipart_field_file = rust::model_gen::multipart_field_module()?;
116 std::fs::write(
117 model.join(multipart_field_file.def.name.file_name()),
118 multipart_field_file.code,
119 )
120 .unwrap();
121 models.push(multipart_field_file.def);
122
123 while !ref_cache.is_empty() {
124 let mut next_ref_cache = RefCache::new();
125
126 for ref_str in ref_cache.refs {
127 if !known_refs.refs.contains(&ref_str) {
128 let model_file =
129 rust::model_gen::model_gen(&ref_str, &open_api, &mapping, &mut next_ref_cache)?;
130 std::fs::write(model.join(model_file.def.name.file_name()), model_file.code)
131 .unwrap();
132 models.push(model_file.def);
133 known_refs.add(ref_str);
134 }
135 }
136
137 let mut unknown_ref_cache = RefCache::new();
138
139 for ref_str in next_ref_cache.refs {
140 if !known_refs.refs.contains(&ref_str) {
141 unknown_ref_cache.add(ref_str)
142 }
143 }
144
145 ref_cache = unknown_ref_cache;
146 }
147
148 std::fs::write(
149 src.join("api.rs"),
150 rust::lib_gen::lib_gen("crate::api", &api_module_defs, disable_clippy),
151 )
152 .unwrap();
153
154 std::fs::write(
155 src.join("model.rs"),
156 rust::lib_gen::lib_gen("crate::model", &models, disable_clippy),
157 )
158 .unwrap();
159
160 let errors = rust::error_gen::error_gen();
161 std::fs::write(src.join(errors.def.name.file_name()), &errors.code).unwrap();
162
163 let module_defs = vec![
164 context.def,
165 ModuleDef::new(ModuleName::new_pub("api")),
166 ModuleDef::new(ModuleName::new_pub("model")),
167 errors.def,
168 ];
169
170 let lib = rust::lib_gen::lib_gen("crate", &module_defs, disable_clippy);
171 std::fs::write(src.join("lib.rs"), lib).unwrap();
172
173 if overwrite_cargo {
174 let cargo = toml::cargo::gen(name, version);
175 std::fs::write(target.join("Cargo.toml"), cargo).unwrap();
176 }
177
178 Ok(())
179}
180
181pub fn parse_openapi_specs(
182 spec: &[PathBuf],
183) -> std::result::Result<Vec<OpenAPI>, Box<dyn std::error::Error>> {
184 spec.iter()
185 .map(|spec_path| {
186 let file = File::open(spec_path)?;
187 let reader = BufReader::new(file);
188 let openapi: OpenAPI = serde_yaml::from_reader(reader)?;
189 Ok(openapi)
190 })
191 .collect()
192}