1use openapiv3::OpenAPI;
10use openapiv3::Operation;
11use openapiv3::PathItem;
12use openapiv3::ReferenceOr;
13
14use crate::config::OutputOptions;
15
16pub fn apply(doc: &mut OpenAPI, opts: &OutputOptions) {
18 filter_operations(doc, opts);
19 exclude_schemas(doc, &opts.exclude_schemas);
20}
21
22fn has_operation_filters(opts: &OutputOptions) -> bool {
24 return !opts.include_tags.is_empty()
25 || !opts.exclude_tags.is_empty()
26 || !opts.include_operation_ids.is_empty()
27 || !opts.exclude_operation_ids.is_empty();
28}
29
30fn filter_operations(doc: &mut OpenAPI, opts: &OutputOptions) {
32 if !has_operation_filters(opts) {
33 return;
34 }
35 for (_, entry) in doc.paths.paths.iter_mut() {
36 let ReferenceOr::Item(item) = entry else {
37 continue;
38 };
39 for slot in operation_slots(item) {
40 let remove = slot.as_ref().is_some_and(|op| {
41 return is_filtered_out(op, opts);
42 });
43 if remove {
44 *slot = None;
45 }
46 }
47 }
48}
49
50fn operation_slots(item: &mut PathItem) -> [&mut Option<Operation>; 8] {
54 return [
55 &mut item.get,
56 &mut item.put,
57 &mut item.post,
58 &mut item.delete,
59 &mut item.options,
60 &mut item.head,
61 &mut item.patch,
62 &mut item.trace,
63 ];
64}
65
66fn is_filtered_out(operation: &Operation, opts: &OutputOptions) -> bool {
73 return removes_operation(opts, &operation.tags, operation.operation_id.as_deref());
74}
75
76pub(crate) fn removes_operation(opts: &OutputOptions, tags: &[String], id: Option<&str>) -> bool {
80 if !opts.exclude_tags.is_empty()
81 && tags.iter().any(|tag| {
82 return opts.exclude_tags.contains(tag);
83 })
84 {
85 return true;
86 }
87 if !opts.include_tags.is_empty()
88 && !tags.iter().any(|tag| {
89 return opts.include_tags.contains(tag);
90 })
91 {
92 return true;
93 }
94 if !opts.exclude_operation_ids.is_empty()
95 && id.is_some_and(|id| return contains_str(&opts.exclude_operation_ids, id))
96 {
97 return true;
98 }
99 if !opts.include_operation_ids.is_empty()
100 && !id.is_some_and(|id| return contains_str(&opts.include_operation_ids, id))
101 {
102 return true;
103 }
104 return false;
105}
106
107fn contains_str(values: &[String], needle: &str) -> bool {
109 return values.iter().any(|value| {
110 return value == needle;
111 });
112}
113
114fn exclude_schemas(doc: &mut OpenAPI, exclude: &[String]) {
116 if exclude.is_empty() {
117 return;
118 }
119 if let Some(components) = doc.components.as_mut() {
120 components.schemas.retain(|name, _| {
121 return !exclude.contains(name);
122 });
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 #[test]
135 fn operation_slots_cover_every_verb() {
136 let populated = || {
137 return Some(Operation::default());
138 };
139 let mut item = PathItem {
140 get: populated(),
141 put: populated(),
142 post: populated(),
143 delete: populated(),
144 options: populated(),
145 head: populated(),
146 patch: populated(),
147 trace: populated(),
148 ..Default::default()
149 };
150 assert_eq!(
151 item.iter().count(),
152 operation_slots(&mut item).len(),
153 "every populated verb should map to one slot"
154 );
155 for slot in operation_slots(&mut item) {
156 *slot = None;
157 }
158 assert_eq!(
159 item.iter().count(),
160 0,
161 "operation_slots must cover every verb reported by openapiv3's PathItem::iter",
162 );
163 }
164}