Skip to main content

sova_openapi/
undocumented.rs

1use crate::doc::Doc;
2use crate::validate_meta::OpenApiValidate;
3use sova_core::extend::{RouteEntry, RouteTable};
4use sova_core::App;
5
6/// Routes that lack a [`Doc`] / [`OpenApiValidate`] (and are not skip / under docs).
7pub fn undocumented(app: &App) -> Vec<String> {
8    undocumented_with_prefix(app, "/docs")
9}
10
11pub fn undocumented_with_prefix(app: &App, docs_prefix: &str) -> Vec<String> {
12    let entries = app.route_entries();
13    undocumented_entries(entries.iter(), docs_prefix)
14}
15
16/// Same as [`undocumented`], using a compiled [`RouteTable`] (e.g. from `check` state).
17pub fn undocumented_from_table(table: &RouteTable, docs_prefix: &str) -> Vec<String> {
18    undocumented_entries(table.0.iter(), docs_prefix)
19}
20
21fn undocumented_entries<'a>(
22    entries: impl IntoIterator<Item = &'a RouteEntry>,
23    docs_prefix: &str,
24) -> Vec<String> {
25    let prefix = docs_prefix.trim_end_matches('/');
26    let mut out = Vec::new();
27    for entry in entries {
28        let RouteEntry::Http {
29            method,
30            path,
31            meta,
32        } = entry
33        else {
34            continue;
35        };
36        if path == prefix || path.starts_with(&format!("{prefix}/")) {
37            continue;
38        }
39        if meta.get::<OpenApiValidate>().is_some() {
40            continue;
41        }
42        match meta.get::<Doc>() {
43            Some(d) if d.is_skip() => continue,
44            Some(_) => continue,
45            None => out.push(format!("{method} {path}")),
46        }
47    }
48    out.sort();
49    out
50}