Skip to main content

baukit_openapi/
response_descriptions.rs

1use std::collections::BTreeMap;
2
3use http::StatusCode;
4use utoipa::openapi::{
5    OpenApi, RefOr,
6    path::{Callback, PathItem},
7    response::Response,
8};
9
10/// Fills empty response descriptions so utoipa emits OpenAPI's required field.
11///
12/// Uses the HTTP reason phrase for known status codes. Default, range, unknown
13/// status codes, and reusable responses without a status use "Response".
14/// Covers paths, webhooks, callbacks, and reusable components. References and
15/// non-empty product descriptions stay intact. Applying this twice has no effect.
16pub fn fill_missing_response_descriptions(openapi: &mut OpenApi) {
17    for item in openapi.paths.paths.values_mut() {
18        fill_path_item(item);
19    }
20    if let Some(webhooks) = &mut openapi.webhooks {
21        for item in webhooks.paths.values_mut() {
22            fill_path_item(item);
23        }
24    }
25    if let Some(components) = &mut openapi.components {
26        fill_responses(&mut components.responses);
27        fill_callbacks(&mut components.callbacks);
28        for item in components.path_items.values_mut() {
29            fill_path_item(item);
30        }
31    }
32}
33
34fn fill_path_item(item: &mut PathItem) {
35    for operation in [
36        &mut item.get,
37        &mut item.put,
38        &mut item.post,
39        &mut item.delete,
40        &mut item.options,
41        &mut item.head,
42        &mut item.patch,
43        &mut item.trace,
44        &mut item.query,
45    ]
46    .into_iter()
47    .flatten()
48    .chain(item.additional_operations.values_mut())
49    {
50        fill_responses(&mut operation.responses.responses);
51        fill_callbacks(&mut operation.callbacks);
52    }
53}
54
55fn fill_callbacks(callbacks: &mut BTreeMap<String, RefOr<Callback>>) {
56    for callback in callbacks.values_mut() {
57        if let RefOr::T(callback) = callback {
58            for item in callback.values_mut() {
59                if let RefOr::T(item) = item {
60                    fill_path_item(item);
61                }
62            }
63        }
64    }
65}
66
67fn fill_responses(responses: &mut BTreeMap<String, RefOr<Response>>) {
68    for (status, response) in responses {
69        if let RefOr::T(response) = response
70            && response.description.is_empty()
71        {
72            response.description = StatusCode::from_bytes(status.as_bytes())
73                .ok()
74                .and_then(|status| status.canonical_reason())
75                .unwrap_or("Response")
76                .to_owned();
77        }
78    }
79}