/// A single query/path/body parameter declared on an API route. #[derive(serde::Serialize, Clone)] pub struct ApiParam { pub name: &'static str, /// Rust-like type hint: string | u32 | bool | i64 | json. pub kind: &'static str, /// "required" | "optional" | "default=" pub constraint: &'static str, pub description: &'static str, } /// Static metadata for an HTTP endpoint. /// /// Registered at link time via [`inventory::submit!`] — generated by /// `#[onto_api(...)]` proc-macro attribute on each handler function. /// Collected via [`inventory::iter::()`]. #[derive(serde::Serialize, Clone)] pub struct ApiRouteEntry { pub method: &'static str, pub path: &'static str, pub description: &'static str, /// Authentication required: "none" | "viewer" | "bearer" | "admin" pub auth: &'static str, /// Which actors typically call this endpoint. pub actors: &'static [&'static str], pub params: &'static [ApiParam], /// Semantic grouping tags (e.g. "graph", "federation", "describe"). pub tags: &'static [&'static str], /// Non-empty when the endpoint is only compiled under a feature flag. pub feature: &'static str, } inventory::collect!(ApiRouteEntry); /// Serialize all statically-registered [`ApiRouteEntry`] items to a /// pretty-printed JSON array, sorted by path then method. /// /// Intended for daemon binaries that expose a `--dump-api-catalog` flag: write /// the output to `api-catalog.json` in the project root so the ontoref UI can /// display the API surface of consumer projects that run as separate processes. pub fn dump_catalog_json() -> String { let mut routes: Vec<&'static ApiRouteEntry> = inventory::iter::().collect(); routes.sort_by(|a, b| a.path.cmp(b.path).then(a.method.cmp(b.method))); serde_json::to_string_pretty(&routes).unwrap_or_else(|_| "[]".to_string()) }