use openapiv3::OpenAPI;
use openapiv3::Operation;
use openapiv3::PathItem;
use openapiv3::ReferenceOr;
use crate::config::OutputOptions;
pub fn apply(doc: &mut OpenAPI, opts: &OutputOptions) {
filter_operations(doc, opts);
exclude_schemas(doc, &opts.exclude_schemas);
}
fn has_operation_filters(opts: &OutputOptions) -> bool {
return !opts.include_tags.is_empty()
|| !opts.exclude_tags.is_empty()
|| !opts.include_operation_ids.is_empty()
|| !opts.exclude_operation_ids.is_empty();
}
fn filter_operations(doc: &mut OpenAPI, opts: &OutputOptions) {
if !has_operation_filters(opts) {
return;
}
for (_, entry) in doc.paths.paths.iter_mut() {
let ReferenceOr::Item(item) = entry else {
continue;
};
for slot in operation_slots(item) {
let remove = slot.as_ref().is_some_and(|op| {
return is_filtered_out(op, opts);
});
if remove {
*slot = None;
}
}
}
}
fn operation_slots(item: &mut PathItem) -> [&mut Option<Operation>; 8] {
return [
&mut item.get,
&mut item.put,
&mut item.post,
&mut item.delete,
&mut item.options,
&mut item.head,
&mut item.patch,
&mut item.trace,
];
}
fn is_filtered_out(operation: &Operation, opts: &OutputOptions) -> bool {
if !opts.exclude_tags.is_empty()
&& operation.tags.iter().any(|tag| {
return opts.exclude_tags.contains(tag);
})
{
return true;
}
if !opts.include_tags.is_empty()
&& !operation.tags.iter().any(|tag| {
return opts.include_tags.contains(tag);
})
{
return true;
}
let id = operation.operation_id.as_deref();
if !opts.exclude_operation_ids.is_empty()
&& id.is_some_and(|id| return contains_str(&opts.exclude_operation_ids, id))
{
return true;
}
if !opts.include_operation_ids.is_empty()
&& !id.is_some_and(|id| return contains_str(&opts.include_operation_ids, id))
{
return true;
}
return false;
}
fn contains_str(values: &[String], needle: &str) -> bool {
return values.iter().any(|value| {
return value == needle;
});
}
fn exclude_schemas(doc: &mut OpenAPI, exclude: &[String]) {
if exclude.is_empty() {
return;
}
if let Some(components) = doc.components.as_mut() {
components.schemas.retain(|name, _| {
return !exclude.contains(name);
});
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn operation_slots_cover_every_verb() {
let populated = || {
return Some(Operation::default());
};
let mut item = PathItem {
get: populated(),
put: populated(),
post: populated(),
delete: populated(),
options: populated(),
head: populated(),
patch: populated(),
trace: populated(),
..Default::default()
};
assert_eq!(
item.iter().count(),
operation_slots(&mut item).len(),
"every populated verb should map to one slot"
);
for slot in operation_slots(&mut item) {
*slot = None;
}
assert_eq!(
item.iter().count(),
0,
"operation_slots must cover every verb reported by openapiv3's PathItem::iter",
);
}
}