1use http::Method;
6use schemars::gen::SchemaGenerator;
7use schemars::schema::SchemaObject;
8use serde::{Deserialize, Serialize};
9use serde_json::Value;
10use std::collections::{hash_map::Entry as HashEntry, HashMap};
11use std::iter::FromIterator;
12
13pub type Map<K, V> = schemars::Map<K, V>;
14
15type Object = Map<String, Value>;
16pub type SecurityRequirement = Map<String, Vec<String>>;
17
18#[derive(Debug, Clone)]
19pub struct OpenApiGenerator {
20 pub schema_generator: SchemaGenerator,
21 pub operations: HashMap<(String, http::Method), Operation>,
22}
23impl OpenApiGenerator {
24 pub fn new(generator: SchemaGenerator) -> Self {
25 OpenApiGenerator {
26 schema_generator: generator,
27 operations: std::collections::HashMap::default(),
28 }
29 }
30 pub fn add_operation(&mut self, mut op: OperationInfo) {
31 if let Some(op_id) = op.operation.operation_id {
32 op.operation.operation_id = Some(op_id.trim_start_matches(':').replace("::", "_"));
34 }
35 match self.operations.entry((op.path, op.method)) {
36 HashEntry::Occupied(e) => {
37 let (path, method) = e.key();
38 panic!(
39 "An OpenAPI operation has already been added for {} {}",
40 method, path
41 );
42 }
43 HashEntry::Vacant(e) => e.insert(op.operation),
44 };
45 }
46
47 pub fn into_openapi(self) -> OpenApi {
48 OpenApi {
49 openapi: "3.0.0".to_owned(),
50 paths: {
51 let mut paths = Map::new();
52 for ((path, method), op) in self.operations {
53 let path_item: &mut PathItem = paths.entry(path).or_default();
54 Self::set_operation(path_item, &method, op);
55 }
56 paths
57 },
58 components: Some(Components {
59 schemas: Map::from_iter(
60 self.schema_generator
61 .clone()
62 .take_definitions()
63 .into_iter()
64 .map(|(k, v)| (k, v.into())),
65 ),
66 ..Components::default()
67 }),
68 ..OpenApi::default()
69 }
70 }
71
72 fn set_operation(path_item: &mut PathItem, method: &http::Method, op: Operation) {
73 let option = match *method {
75 Method::GET => &mut path_item.get,
76 Method::PUT => &mut path_item.put,
77 Method::POST => &mut path_item.post,
78 Method::DELETE => &mut path_item.delete,
79 Method::OPTIONS => &mut path_item.options,
80 Method::HEAD => &mut path_item.head,
81 Method::PATCH => &mut path_item.patch,
82 Method::TRACE => &mut path_item.trace,
83 _ => return,
86 };
87 assert!(option.is_none());
88 option.replace(op);
89 }
90}
91
92pub struct OperationInfo {
93 pub path: String,
94 pub method: http::Method,
95 pub operation: Operation,
96}
97
98#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
99#[serde(untagged)]
100pub enum RefOr<T> {
101 Ref(Ref),
102 Object(T),
103}
104
105#[derive(Serialize, Deserialize, Debug, Clone, PartialEq)]
106
107pub struct Ref {
108 #[serde(rename = "$ref")]
109 pub reference: String,
110}
111
112impl<T> From<T> for RefOr<T> {
113 fn from(o: T) -> Self {
114 RefOr::<T>::Object(o)
115 }
116}
117
118#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
119#[serde(rename_all = "camelCase")]
120pub struct OpenApi {
121 pub openapi: String,
122 pub info: Info,
123 #[serde(default, skip_serializing_if = "Vec::is_empty")]
124 pub servers: Vec<Server>,
125 pub paths: Map<String, PathItem>,
126 #[serde(default, skip_serializing_if = "Option::is_none")]
127 pub components: Option<Components>,
128 #[serde(default, skip_serializing_if = "Vec::is_empty")]
129 pub security: Vec<SecurityRequirement>,
130 #[serde(default, skip_serializing_if = "Vec::is_empty")]
131 pub tags: Vec<Tag>,
132 #[serde(default, skip_serializing_if = "Option::is_none")]
133 pub external_docs: Option<ExternalDocs>,
134 #[serde(flatten)]
135 pub extensions: Object,
136}
137
138#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
139#[serde(rename_all = "camelCase")]
140pub struct Info {
141 pub title: String,
142 #[serde(default, skip_serializing_if = "Option::is_none")]
143 pub description: Option<String>,
144 #[serde(default, skip_serializing_if = "Option::is_none")]
145 pub terms_of_service: Option<String>,
146 #[serde(default, skip_serializing_if = "Option::is_none")]
147 pub contact: Option<Contact>,
148 #[serde(default, skip_serializing_if = "Option::is_none")]
149 pub license: Option<License>,
150 pub version: String,
151 #[serde(flatten)]
152 pub extensions: Object,
153}
154
155#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
156#[serde(default, rename_all = "camelCase")]
157pub struct Contact {
158 #[serde(skip_serializing_if = "Option::is_none")]
159 pub name: Option<String>,
160 #[serde(skip_serializing_if = "Option::is_none")]
161 pub url: Option<String>,
162 #[serde(skip_serializing_if = "Option::is_none")]
163 pub email: Option<String>,
164 #[serde(flatten)]
165 pub extensions: Object,
166}
167
168#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
169#[serde(rename_all = "camelCase")]
170pub struct License {
171 pub name: String,
172 #[serde(default, skip_serializing_if = "Option::is_none")]
173 pub url: Option<String>,
174 #[serde(flatten)]
175 pub extensions: Object,
176}
177
178#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
179#[serde(rename_all = "camelCase")]
180pub struct Server {
181 pub url: String,
182 #[serde(default, skip_serializing_if = "Option::is_none")]
183 pub description: Option<String>,
184 #[serde(default, skip_serializing_if = "Map::is_empty")]
185 pub variables: Map<String, ServerVariable>,
186 #[serde(flatten)]
187 pub extensions: Object,
188}
189
190#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
191#[serde(rename_all = "camelCase")]
192pub struct ServerVariable {
193 #[serde(default, rename = "enum", skip_serializing_if = "Option::is_none")]
194 pub enumeration: Option<Vec<String>>,
195 pub default: String,
196 #[serde(default, skip_serializing_if = "Option::is_none")]
197 pub description: Option<String>,
198 #[serde(flatten)]
199 pub extensions: Object,
200}
201
202#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
203#[serde(default, rename_all = "camelCase")]
204pub struct PathItem {
205 #[serde(default, rename = "$ref", skip_serializing_if = "Option::is_none")]
206 pub reference: Option<String>,
207 #[serde(default, skip_serializing_if = "Option::is_none")]
208 pub summary: Option<String>,
209 #[serde(default, skip_serializing_if = "Option::is_none")]
210 pub description: Option<String>,
211 #[serde(default, skip_serializing_if = "Option::is_none")]
212 pub get: Option<Operation>,
213 #[serde(default, skip_serializing_if = "Option::is_none")]
214 pub put: Option<Operation>,
215 #[serde(default, skip_serializing_if = "Option::is_none")]
216 pub post: Option<Operation>,
217 #[serde(default, skip_serializing_if = "Option::is_none")]
218 pub delete: Option<Operation>,
219 #[serde(default, skip_serializing_if = "Option::is_none")]
220 pub options: Option<Operation>,
221 #[serde(default, skip_serializing_if = "Option::is_none")]
222 pub head: Option<Operation>,
223 #[serde(default, skip_serializing_if = "Option::is_none")]
224 pub patch: Option<Operation>,
225 #[serde(default, skip_serializing_if = "Option::is_none")]
226 pub trace: Option<Operation>,
227 #[serde(default, skip_serializing_if = "Option::is_none")]
228 pub servers: Option<Vec<Server>>,
229 #[serde(default, skip_serializing_if = "Vec::is_empty")]
230 pub parameters: Vec<RefOr<Parameter>>,
231 #[serde(flatten)]
232 pub extensions: Object,
233}
234
235#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
236#[serde(rename_all = "camelCase")]
237pub struct Operation {
238 #[serde(default, skip_serializing_if = "Vec::is_empty")]
239 pub tags: Vec<String>,
240 #[serde(default, skip_serializing_if = "Option::is_none")]
241 pub summary: Option<String>,
242 #[serde(default, skip_serializing_if = "Option::is_none")]
243 pub description: Option<String>,
244 #[serde(default, skip_serializing_if = "Option::is_none")]
245 pub external_docs: Option<ExternalDocs>,
246 #[serde(default, skip_serializing_if = "Option::is_none")]
247 pub operation_id: Option<String>,
248 #[serde(default, skip_serializing_if = "Vec::is_empty")]
249 pub parameters: Vec<RefOr<Parameter>>,
250 #[serde(default, skip_serializing_if = "Option::is_none")]
251 pub request_body: Option<RefOr<RequestBody>>,
252 pub responses: Responses,
253 #[serde(default, skip_serializing_if = "Map::is_empty")]
254 pub callbacks: Map<String, RefOr<Callback>>,
255 #[serde(default, skip_serializing_if = "is_false")]
256 pub deprecated: bool,
257 #[serde(default, skip_serializing_if = "Option::is_none")]
258 pub security: Option<Vec<SecurityRequirement>>,
259 #[serde(default, skip_serializing_if = "Option::is_none")]
260 pub servers: Option<Vec<Server>>,
261 #[serde(flatten)]
262 pub extensions: Object,
263}
264
265#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
266#[serde(default, rename_all = "camelCase")]
267pub struct Responses {
268 #[serde(skip_serializing_if = "Option::is_none")]
269 pub default: Option<RefOr<Response>>,
270 #[serde(flatten)]
271 pub responses: Map<String, RefOr<Response>>,
272 #[serde(flatten)]
273 pub extensions: Object,
274}
275
276#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
277#[serde(default, rename_all = "camelCase")]
278pub struct Components {
279 #[serde(default, skip_serializing_if = "Map::is_empty")]
280 pub schemas: Map<String, SchemaObject>,
281 #[serde(default, skip_serializing_if = "Map::is_empty")]
282 pub responses: Map<String, RefOr<Response>>,
283 #[serde(default, skip_serializing_if = "Map::is_empty")]
284 pub parameters: Map<String, RefOr<Parameter>>,
285 #[serde(default, skip_serializing_if = "Map::is_empty")]
286 pub examples: Map<String, RefOr<Example>>,
287 #[serde(default, skip_serializing_if = "Map::is_empty")]
288 pub request_bodies: Map<String, RefOr<RequestBody>>,
289 #[serde(default, skip_serializing_if = "Map::is_empty")]
290 pub headers: Map<String, RefOr<Header>>,
291 #[serde(default, skip_serializing_if = "Map::is_empty")]
292 pub security_schemes: Map<String, RefOr<SecurityScheme>>,
293 #[serde(default, skip_serializing_if = "Map::is_empty")]
294 pub links: Map<String, RefOr<Link>>,
295 #[serde(default, skip_serializing_if = "Map::is_empty")]
296 pub callbacks: Map<String, RefOr<Callback>>,
297 #[serde(flatten)]
298 pub extensions: Object,
299}
300
301#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
302#[serde(rename_all = "camelCase")]
303pub struct Response {
304 pub description: String,
305 #[serde(default, skip_serializing_if = "Map::is_empty")]
306 pub headers: Map<String, RefOr<Header>>,
307 #[serde(default, skip_serializing_if = "Map::is_empty")]
308 pub content: Map<String, MediaType>,
309 #[serde(default, skip_serializing_if = "Map::is_empty")]
310 pub links: Map<String, RefOr<Link>>,
311 #[serde(flatten)]
312 pub extensions: Object,
313}
314
315#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
316#[serde(rename_all = "camelCase")]
317pub struct Parameter {
318 pub name: String,
319 #[serde(rename = "in")]
321 pub location: String,
322 #[serde(default, skip_serializing_if = "Option::is_none")]
323 pub description: Option<String>,
324 #[serde(default, skip_serializing_if = "is_false")]
325 pub required: bool,
326 #[serde(default, skip_serializing_if = "is_false")]
327 pub deprecated: bool,
328 #[serde(default)]
329 pub allow_empty_value: bool,
330 #[serde(flatten)]
331 pub value: ParameterValue,
332 #[serde(flatten)]
333 pub extensions: Object,
334}
335
336#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
338#[serde(untagged, rename_all = "camelCase")]
339pub enum ParameterValue {
340 Schema {
341 #[serde(default, skip_serializing_if = "Option::is_none")]
342 style: Option<ParameterStyle>,
343 #[serde(default, skip_serializing_if = "Option::is_none")]
344 explode: Option<bool>,
345 #[serde(default, skip_serializing_if = "is_false")]
346 allow_reserved: bool,
347 schema: Box<SchemaObject>,
348 #[serde(default, skip_serializing_if = "Option::is_none")]
349 example: Option<Value>,
350 #[serde(default, skip_serializing_if = "Option::is_none")]
351 examples: Option<Map<String, Example>>,
352 },
353 Content {
354 content: Map<String, MediaType>,
355 },
356}
357
358#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
359#[serde(rename_all = "camelCase")]
360pub enum ParameterStyle {
361 Matrix,
362 Label,
363 Form,
364 Simple,
365 SpaceDelimited,
366 PipeDelimited,
367 DeepObject,
368}
369
370#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
371#[serde(rename_all = "camelCase")]
372pub struct Example {
373 #[serde(default, skip_serializing_if = "Option::is_none")]
374 pub summary: Option<String>,
375 #[serde(default, skip_serializing_if = "Option::is_none")]
376 pub description: Option<String>,
377 #[serde(flatten)]
378 pub value: ExampleValue,
379 #[serde(flatten)]
380 pub extensions: Object,
381}
382
383#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
384#[serde(rename_all = "camelCase")]
385pub enum ExampleValue {
386 Value(Value),
387 ExternalValue(String),
388}
389
390#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
391#[serde(rename_all = "camelCase")]
392pub struct RequestBody {
393 #[serde(default, skip_serializing_if = "Option::is_none")]
394 pub description: Option<String>,
395 pub content: Map<String, MediaType>,
396 #[serde(default, skip_serializing_if = "is_false")]
397 pub required: bool,
398 #[serde(flatten)]
399 pub extensions: Object,
400}
401
402#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
403#[serde(rename_all = "camelCase")]
404pub struct Header {
405 #[serde(default, skip_serializing_if = "Option::is_none")]
406 pub description: Option<String>,
407 #[serde(default, skip_serializing_if = "is_false")]
408 pub required: bool,
409 #[serde(default, skip_serializing_if = "is_false")]
410 pub deprecated: bool,
411 #[serde(default, skip_serializing_if = "is_false")]
412 pub allow_empty_value: bool,
413 #[serde(flatten)]
414 pub value: ParameterValue,
415 #[serde(flatten)]
416 pub extensions: Object,
417}
418
419#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
420#[serde(rename_all = "camelCase")]
421pub struct SecurityScheme {
422 #[serde(rename = "type")]
423 #[serde(default, skip_serializing_if = "Option::is_none")]
424 pub schema_type: Option<String>,
425 #[serde(default, skip_serializing_if = "Option::is_none")]
426 pub description: Option<String>,
427 #[serde(flatten)]
428 pub data: SecuritySchemeData,
429 #[serde(flatten)]
430 pub extensions: Object,
431}
432
433#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
434#[serde(tag = "type", rename_all = "camelCase")]
435pub enum SecuritySchemeData {
436 ApiKey {
437 name: String,
438 #[serde(rename = "in")]
439 location: String,
440 },
441 Http {
442 scheme: String,
443 #[serde(rename = "bearerFormat")]
444 #[serde(default, skip_serializing_if = "Option::is_none")]
445 bearer_format: Option<String>,
446 },
447 #[serde(rename = "oauth2")]
448 OAuth2 {
449 flows: Box<OAuthFlows>,
450 },
451 OpenIdConnect {
452 open_id_connect_url: String,
453 },
454}
455
456#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
457#[serde(default, rename_all = "camelCase")]
458pub struct OAuthFlows {
459 #[serde(skip_serializing_if = "Option::is_none")]
460 pub implicit: Option<OAuthFlow>,
461 #[serde(skip_serializing_if = "Option::is_none")]
462 pub password: Option<OAuthFlow>,
463 #[serde(skip_serializing_if = "Option::is_none")]
464 pub client_credentials: Option<OAuthFlow>,
465 #[serde(skip_serializing_if = "Option::is_none")]
466 pub authorization_code: Option<OAuthFlow>,
467 #[serde(flatten)]
468 pub extensions: Object,
469}
470
471#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
472#[serde(rename_all = "camelCase")]
473pub struct OAuthFlow {
474 pub authorization_url: String,
475 pub token_url: String,
476 #[serde(default, skip_serializing_if = "Option::is_none")]
477 pub refresh_url: Option<String>,
478 pub scopes: Map<String, String>,
479 #[serde(flatten)]
480 pub extensions: Object,
481}
482
483#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
484#[serde(rename_all = "camelCase")]
485pub struct Link {
486 #[serde(default, skip_serializing_if = "Option::is_none")]
488 pub operation_ref: Option<String>,
489 #[serde(default, skip_serializing_if = "Option::is_none")]
490 pub operation_id: Option<String>,
491 #[serde(default, skip_serializing_if = "Map::is_empty")]
492 pub parameters: Map<String, Value>,
493 #[serde(default, skip_serializing_if = "Option::is_none")]
494 pub request_body: Option<Value>,
495 #[serde(default, skip_serializing_if = "Option::is_none")]
496 pub description: Option<String>,
497 #[serde(default, skip_serializing_if = "Option::is_none")]
498 pub server: Option<Server>,
499 #[serde(flatten)]
500 pub extensions: Object,
501}
502
503#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
504#[serde(rename_all = "camelCase")]
505pub struct Callback {
506 #[serde(flatten)]
507 pub callbacks: Map<String, PathItem>,
508 #[serde(flatten)]
509 pub extensions: Object,
510}
511
512#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
513#[serde(default, rename_all = "camelCase")]
514pub struct MediaType {
515 #[serde(skip_serializing_if = "Option::is_none")]
516 pub schema: Option<SchemaObject>,
517 #[serde(skip_serializing_if = "Option::is_none")]
518 pub example: Option<Value>,
519 #[serde(skip_serializing_if = "Option::is_none")]
520 pub examples: Option<Map<String, Example>>,
521 #[serde(skip_serializing_if = "Map::is_empty")]
522 pub encoding: Map<String, Encoding>,
523 #[serde(flatten)]
524 pub extensions: Object,
525}
526
527#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
528#[serde(rename_all = "camelCase")]
529pub struct Tag {
530 pub name: String,
531 #[serde(default, skip_serializing_if = "Option::is_none")]
532 pub description: Option<String>,
533 #[serde(default, skip_serializing_if = "Option::is_none")]
534 pub external_docs: Option<ExternalDocs>,
535 #[serde(flatten)]
536 pub extensions: Object,
537}
538
539#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
540#[serde(rename_all = "camelCase")]
541pub struct ExternalDocs {
542 #[serde(default, skip_serializing_if = "Option::is_none")]
543 pub description: Option<String>,
544 pub url: String,
545 #[serde(flatten)]
546 pub extensions: Object,
547}
548
549#[derive(Debug, Clone, PartialEq, Deserialize, Serialize, Default)]
550#[serde(default, rename_all = "camelCase")]
551pub struct Encoding {
552 #[serde(skip_serializing_if = "Option::is_none")]
553 pub content_type: Option<String>,
554 #[serde(skip_serializing_if = "Map::is_empty")]
555 pub headers: Map<String, RefOr<Header>>,
556 #[serde(skip_serializing_if = "Option::is_none")]
557 pub style: Option<String>,
558 #[serde(skip_serializing_if = "Option::is_none")]
559 pub explode: Option<bool>,
560 #[serde(skip_serializing_if = "is_false")]
561 pub allow_reserved: bool,
562 #[serde(flatten)]
563 pub extensions: Object,
564}
565
566fn is_false(b: impl std::borrow::Borrow<bool>) -> bool {
567 !b.borrow()
568}