openapi_codegen/client/
mod.rs1use crate::client::api::Api;
2use crate::client::api::Method;
3use crate::client::model::DataType;
4use failure::Error;
5use handlebars::Handlebars;
6use openapiv3::OpenAPI;
7use openapiv3::Operation;
8use openapiv3::Parameter;
9use openapiv3::PathItem;
10use openapiv3::ReferenceOr;
11use serde_derive::Serialize;
12use serde_yaml;
13use std::collections::HashSet;
14use std::fs::{DirBuilder, File};
15use std::io::Write;
16use std::path::Path;
17
18mod api;
19mod model;
20
21pub fn client(api_path: &str, output_dir: &str, tests: bool) -> Result<(), Error> {
22 let mut reg = Handlebars::new();
23 reg.register_escape_fn(handlebars::no_escape);
24 reg.register_template_string("api_mod", include_str!("resources/api_mod.mustache"))?;
25 reg.register_template_string("api", include_str!("resources/api.mustache"))?;
26 reg.register_template_string("model_enum", include_str!("resources/model_enum.mustache"))?;
27 reg.register_template_string("model_mod", include_str!("resources/model_mod.mustache"))?;
28 reg.register_template_string(
29 "model_struct",
30 include_str!("resources/model_struct.mustache"),
31 )?;
32 reg.register_template_string(
33 "model_newtype",
34 include_str!("resources/model_newtype.mustache"),
35 )?;
36 reg.register_template_string("mod", include_str!("resources/mod.mustache"))?;
37
38 let dest_path = Path::new(&output_dir);
39 DirBuilder::new().recursive(true).create(&dest_path)?;
40
41 let spec: OpenAPI = serde_yaml::from_reader(File::open(api_path)?)?;
42
43 DirBuilder::new()
44 .recursive(true)
45 .create(&dest_path.join("apis"))?;
46
47 let mut configuration = File::create(&dest_path.join("apis/configuration.rs"))?;
48 configuration.write_all(include_bytes!("resources/configuration.rs"))?;
49
50 let mut request = File::create(&dest_path.join("apis/request.rs"))?;
51 request.write_all(include_bytes!("resources/request.rs"))?;
52
53 let apis = spec_apis(&spec, tests);
54
55 let api_mod = File::create(&dest_path.join("apis/mod.rs"))?;
56 reg.render_to_write("api_mod", &apis, api_mod)?;
57
58 for api in &apis {
59 let api_file = File::create(&dest_path.join(format!("apis/{}_api.rs", api.snake_id)))?;
60 reg.render_to_write("api", &api, api_file)?;
61 }
62
63 let models = match spec.components {
64 Some(components) => components
65 .schemas
66 .into_iter()
67 .map(|entry| entry.into())
68 .collect::<Vec<DataType>>(),
69 None => vec![],
70 };
71
72 let models_path = dest_path.join("models");
73 DirBuilder::new().recursive(true).create(&models_path)?;
74
75 let models_mod = File::create(models_path.join("mod.rs"))?;
76 reg.render_to_write("model_mod", &models, models_mod)?;
77
78 for model in &models {
79 match model {
80 DataType::Struct(_struct) => {
81 let model = File::create(models_path.join(format!("{}.rs", _struct.snake_id)))?;
82 reg.render_to_write("model_struct", &_struct, model)?;
83 }
84 DataType::NewType(newtype) => {
85 let model = File::create(models_path.join(format!("{}.rs", newtype.snake_id)))?;
86 reg.render_to_write("model_newtype", &newtype, model)?;
87 }
88 DataType::Enum(_enum) => {
89 let model = File::create(models_path.join(format!("{}.rs", _enum.snake_id)))?;
90 reg.render_to_write("model_enum", &_enum, model)?;
91 }
92 }
93 }
94
95 let mod_file = if output_dir == "src" {
96 File::create(dest_path.join("lib.rs"))?
97 } else {
98 File::create(dest_path.join("mod.rs"))?
99 };
100
101 let r#mod = Mod {
102 root: output_dir == "src",
103 };
104
105 reg.render_to_write("mod", &r#mod, mod_file)?;
106
107 Ok(())
108}
109
110#[derive(Debug, Serialize)]
111struct Mod {
112 root: bool,
113}
114
115fn spec_apis(spec: &OpenAPI, tests: bool) -> Vec<Api> {
116 paths_tags(spec)
117 .into_iter()
118 .map(|tag| Api {
119 snake_id: tag.clone().unwrap_or("untagged".to_string()).into(),
120 pascal_id: tag.clone().unwrap_or("untagged".to_string()).into(),
121 methods: spec
122 .paths
123 .iter()
124 .filter(|(_path, reference_or_operations)| {
125 operations_tags(reference_or_operations).contains(&tag)
126 })
127 .flat_map(|(path, reference_or_operations)| {
128 operations_methods(path, reference_or_operations)
129 })
130 .collect(),
131 tests,
132 })
133 .collect()
134}
135
136fn operations_methods(path: &str, reference_or_operations: &ReferenceOr<PathItem>) -> Vec<Method> {
137 match reference_or_operations {
138 ReferenceOr::Reference { .. } => unimplemented!(),
139 ReferenceOr::Item(operations) => {
140 let options =
141 vec![
142 operations.get.as_ref().map(|operation| {
143 operation_method("GET".into(), path.to_owned(), &operation)
144 }),
145 operations.post.as_ref().map(|operation| {
146 operation_method("POST".into(), path.to_owned(), &operation)
147 }),
148 operations.put.as_ref().map(|operation| {
149 operation_method("PUT".into(), path.to_owned(), &operation)
150 }),
151 operations.patch.as_ref().map(|operation| {
152 operation_method("PATCH".into(), path.to_owned(), &operation)
153 }),
154 operations.delete.as_ref().map(|operation| {
155 operation_method("DELETE".into(), path.to_owned(), &operation)
156 }),
157 ];
158
159 options.into_iter().filter_map(|o| o).collect()
160 }
161 }
162}
163
164fn operation_method(method: String, path: String, operation: &Operation) -> Method {
165 Method {
166 snake_id: match operation.operation_id.as_ref() {
167 Some(operation_id) => operation_id.to_owned(),
168 None => format!("{}/{}", method, path),
169 }
170 .into(),
171 path: path,
172 http_method: method,
173 path_parameters: operation
174 .parameters
175 .iter()
176 .filter_map(|reference_or_parameter| {
177 if let ReferenceOr::Item(parameter) = reference_or_parameter {
178 Some(parameter)
179 } else {
180 None
181 }
182 })
183 .filter_map(|parameter| {
184 if let Parameter::Path { parameter_data, .. } = parameter {
185 Some(parameter_data)
186 } else {
187 None
188 }
189 })
190 .map(|parameter_data| parameter_data.into())
191 .collect(),
192 query_parameters: operation
193 .parameters
194 .iter()
195 .filter_map(|reference_or_parameter| {
196 if let ReferenceOr::Item(parameter) = reference_or_parameter {
197 Some(parameter)
198 } else {
199 None
200 }
201 })
202 .filter_map(|parameter| {
203 if let Parameter::Query { parameter_data, .. } = parameter {
204 Some(parameter_data)
205 } else {
206 None
207 }
208 })
209 .map(|parameter_data| parameter_data.into())
210 .collect(),
211 body: operation
212 .request_body
213 .as_ref()
214 .map(|parameter| parameter.into()),
215 returns: operation
216 .responses
217 .responses
218 .get("200")
219 .and_then(|reference_or_response| match reference_or_response {
220 ReferenceOr::Item(response) => response.content.get("application/json"),
221 _ => unimplemented!(),
222 })
223 .and_then(|reference_or_mediatype| match reference_or_mediatype {
224 ReferenceOr::Item(mediatype) => mediatype.schema.as_ref(),
225 _ => unimplemented!(),
226 })
227 .map(|reference_or_schema| reference_or_schema.into()),
228 }
229}
230
231fn paths_tags(spec: &OpenAPI) -> HashSet<Option<String>> {
232 spec.paths
233 .iter()
234 .flat_map(|(_, operations)| operations_tags(operations))
235 .collect()
236}
237
238fn operations_tags(reference_or_operations: &ReferenceOr<PathItem>) -> Vec<Option<String>> {
239 match reference_or_operations {
240 ReferenceOr::Reference { .. } => unimplemented!(),
241 ReferenceOr::Item(operations) => {
242 let mut tags = operation_tags(operations.get.as_ref());
243 tags.append(&mut operation_tags(operations.post.as_ref()));
244 tags.append(&mut operation_tags(operations.put.as_ref()));
245 tags.append(&mut operation_tags(operations.patch.as_ref()));
246 tags.append(&mut operation_tags(operations.delete.as_ref()));
247
248 tags
249 }
250 }
251}
252
253fn operation_tags(operation: Option<&Operation>) -> Vec<Option<String>> {
254 match operation {
255 Some(operation) => {
256 if operation.tags.is_empty() {
257 vec![None]
258 } else {
259 operation.tags.iter().map(|s| Some(s.to_string())).collect()
260 }
261 }
262 None => Vec::new(),
263 }
264}