From 22e5aa1d3efc289aa2bff17664adc64515abc49a Mon Sep 17 00:00:00 2001 From: Dietmar Maurer Date: Tue, 4 Jun 2019 13:12:42 +0200 Subject: [PATCH] src/bin/dump-backup-api.rs: helper to generate backup API docs --- src/api_schema/format.rs | 159 +++++++++++++++++++++++++++++++++++++ src/bin/dump-backup-api.rs | 13 +++ 2 files changed, 172 insertions(+) create mode 100644 src/bin/dump-backup-api.rs diff --git a/src/api_schema/format.rs b/src/api_schema/format.rs index ff501686..e4277bd7 100644 --- a/src/api_schema/format.rs +++ b/src/api_schema/format.rs @@ -1,4 +1,8 @@ +use failure::*; + +use std::io::Write; use crate::api_schema::*; +use crate::api_schema::router::*; #[derive(Copy, Clone)] pub enum ParameterDisplayStyle { @@ -134,3 +138,158 @@ pub fn get_property_description( text } } + +fn dump_api_parameters(param: &ObjectSchema) -> String { + + let mut res = wrap_text("", "", param.description, 80); + + let properties = ¶m.properties; + + let mut prop_names: Vec<&str> = properties.keys().map(|v| *v).collect(); + prop_names.sort(); + + let mut required_list: Vec = vec![]; + let mut optional_list: Vec = vec![]; + + for prop in prop_names { + let (optional, schema) = properties.get(prop).unwrap(); + + let param_descr = get_property_description( + prop, &schema, ParameterDisplayStyle::Config, DocumentationFormat::ReST); + + if *optional { + optional_list.push(param_descr); + } else { + required_list.push(param_descr); + } + } + + if required_list.len() > 0 { + + res.push_str("\n*Required properties:*\n\n"); + + for text in required_list { + res.push_str(&text); + res.push('\n'); + } + + } + + if optional_list.len() > 0 { + + res.push_str("\n*Optional properties:*\n\n"); + + for text in optional_list { + res.push_str(&text); + res.push('\n'); + } + } + + res +} + +fn dump_api_return_schema(schema: &Schema) -> String { + + let mut res = String::from("*Returns*: "); + + let type_text = get_schema_type_text(schema, ParameterDisplayStyle::Config); + res.push_str(&format!("**{}**\n\n", type_text)); + + match schema { + Schema::Null => { + return res; + } + Schema::Boolean(schema) => { + let description = wrap_text("", "", schema.description, 80); + res.push_str(&description); + } + Schema::Integer(schema) => { + let description = wrap_text("", "", schema.description, 80); + res.push_str(&description); + } + Schema::String(schema) => { + let description = wrap_text("", "", schema.description, 80); + res.push_str(&description); + } + Schema::Array(schema) => { + let description = wrap_text("", "", schema.description, 80); + res.push_str(&description); + } + Schema::Object(obj_schema) => { + res.push_str(&dump_api_parameters(obj_schema)); + + } + } + + res.push('\n'); + + res +} + +fn dump_method_definition(method: &str, path: &str, def: &MethodDefinition) -> Option { + + match def { + MethodDefinition::None => return None, + MethodDefinition::Simple(simple_method) => { + let param_descr = dump_api_parameters(&simple_method.parameters); + + let return_descr = dump_api_return_schema(&simple_method.returns); + + let res = format!("**{} {}**\n\n{}\n\n{}", method, path, param_descr, return_descr); + return Some(res); + } + MethodDefinition::Async(async_method) => { + let method = if method == "POST" { "UPLOAD" } else { method }; + let method = if method == "GET" { "DOWNLOAD" } else { method }; + + let param_descr = dump_api_parameters(&async_method.parameters); + + let return_descr = dump_api_return_schema(&async_method.returns); + + let res = format!("**{} {}**\n\n{}\n\n{}", method, path, param_descr, return_descr); + return Some(res); + } + } +} + +pub fn dump_api(output: &mut dyn Write, router: &Router, path: &str, mut pos: usize) -> Result<(), Error> { + + let mut cond_print = |x| -> Result<_, Error> { + if let Some(text) = x { + if pos > 0 { + writeln!(output, "-----\n")?; + } + writeln!(output, "{}", text)?; + pos += 1; + } + Ok(()) + }; + + cond_print(dump_method_definition("GET", path, &router.get))?; + cond_print(dump_method_definition("POST", path, &router.post))?; + cond_print(dump_method_definition("PUT", path, &router.put))?; + cond_print(dump_method_definition("DELETE", path, &router.delete))?; + + match &router.subroute { + SubRoute::None => return Ok(()), + SubRoute::MatchAll { router, param_name } => { + let sub_path = if path == "." { + format!("<{}>", param_name) + } else { + format!("{}/<{}>", path, param_name) + }; + dump_api(output, router, &sub_path, pos)?; + } + SubRoute::Hash(map) => { + let mut keys: Vec<&String> = map.keys().collect(); + keys.sort_unstable_by(|a, b| a.cmp(b)); + for key in keys { + let sub_router = &map[key]; + let sub_path = if path == "." { key.to_owned() } else { format!("{}/{}", path, key) }; + dump_api(output, sub_router, &sub_path, pos)?; + } + } + } + + Ok(()) +} diff --git a/src/bin/dump-backup-api.rs b/src/bin/dump-backup-api.rs new file mode 100644 index 00000000..b474f14a --- /dev/null +++ b/src/bin/dump-backup-api.rs @@ -0,0 +1,13 @@ +use failure::*; + +use proxmox_backup::api2; +use proxmox_backup::api_schema::format::*; + +fn main() -> Result<(), Error> { + + let api = api2::admin::datastore::backup::backup_api(); + + dump_api(&mut std::io::stdout(), &api, ".", 0)?; + + Ok(()) +}