]> git.proxmox.com Git - proxmox-backup.git/blob - src/api_schema/format.rs
cf3c23bb4e501df59b2065d849935f221d76596e
[proxmox-backup.git] / src / api_schema / format.rs
1 use failure::*;
2
3 use std::io::Write;
4 //use super::*;
5 use super::router::*;
6 use super::schema::*;
7 //use super::api_handler::*;
8
9
10 #[derive(Copy, Clone)]
11 pub enum ParameterDisplayStyle {
12 Config,
13 //SonfigSub,
14 Arg,
15 Fixed,
16 }
17
18 /// CLI usage information format
19 #[derive(Copy, Clone, PartialEq)]
20 pub enum DocumentationFormat {
21 /// text, command line only (one line)
22 Short,
23 /// text, list all options
24 Long,
25 /// text, include description
26 Full,
27 /// like full, but in reStructuredText format
28 ReST,
29 }
30
31 /// line wrapping to form simple list of paragraphs
32 pub fn wrap_text(initial_indent: &str, subsequent_indent: &str, text: &str, columns: usize) -> String {
33
34 let wrapper1 = textwrap::Wrapper::new(columns)
35 .initial_indent(initial_indent)
36 .subsequent_indent(subsequent_indent);
37
38 let wrapper2 = textwrap::Wrapper::new(columns)
39 .initial_indent(subsequent_indent)
40 .subsequent_indent(subsequent_indent);
41
42 text.split("\n\n")
43 .map(|p| p.trim())
44 .filter(|p| !p.is_empty())
45 .fold(String::new(), |mut acc, p| {
46 if acc.is_empty() {
47 acc.push_str(&wrapper1.wrap(p).concat());
48 } else {
49 acc.push_str(&wrapper2.wrap(p).concat());
50 }
51 acc.push_str("\n\n");
52 acc
53 })
54 }
55
56 pub fn get_schema_type_text(schema: &Schema, _style: ParameterDisplayStyle) -> String {
57 match schema {
58 Schema::Null => String::from("<null>"), // should not happen
59 Schema::String(_) => String::from("<string>"),
60 Schema::Boolean(_) => String::from("<boolean>"),
61 Schema::Integer(integer_schema) => {
62 match (integer_schema.minimum, integer_schema.maximum) {
63 (Some(min), Some(max)) => format!("<integer> ({} - {})", min, max),
64 (Some(min), None) => format!("<integer> ({} - N)", min),
65 (None, Some(max)) => format!("<integer> (-N - {})", max),
66 _ => String::from("<integer>"),
67 }
68 },
69 Schema::Object(_) => String::from("<object>"),
70 Schema::Array(_) => String::from("<array>"),
71 }
72 }
73
74 pub fn get_property_description(
75 name: &str,
76 schema: &Schema,
77 style: ParameterDisplayStyle,
78 format: DocumentationFormat,
79 ) -> String {
80
81 let type_text = get_schema_type_text(schema, style);
82
83 let (descr, default) = match schema {
84 Schema::Null => ("null", None),
85 Schema::String(ref schema) => (schema.description, schema.default.map(|v| v.to_owned())),
86 Schema::Boolean(ref schema) => (schema.description, schema.default.map(|v| v.to_string())),
87 Schema::Integer(ref schema) => (schema.description, schema.default.map(|v| v.to_string())),
88 Schema::Object(ref schema) => (schema.description, None),
89 Schema::Array(ref schema) => (schema.description, None),
90 };
91
92 let default_text = match default {
93 Some(text) => format!(" (default={})", text),
94 None => String::new(),
95 };
96
97 if format == DocumentationFormat::ReST {
98
99 let mut text = match style {
100 ParameterDisplayStyle::Config => {
101 format!(":``{} {}{}``: ", name, type_text, default_text)
102 }
103 ParameterDisplayStyle::Arg => {
104 format!(":``--{} {}{}``: ", name, type_text, default_text)
105 }
106 ParameterDisplayStyle::Fixed => {
107 format!(":``<{}> {}{}``: ", name, type_text, default_text)
108 }
109 };
110
111 text.push_str(&wrap_text("", "", descr, 80));
112 text.push('\n');
113 text.push('\n');
114
115 text
116
117 } else {
118
119 let display_name = match style {
120 ParameterDisplayStyle::Config => {
121 format!("{}:", name)
122 }
123 ParameterDisplayStyle::Arg => {
124 format!("--{}", name)
125 }
126 ParameterDisplayStyle::Fixed => {
127 format!("<{}>", name)
128 }
129 };
130
131 let mut text = format!(" {:-10} {}{}", display_name, type_text, default_text);
132 let indent = " ";
133 text.push('\n');
134 text.push_str(&wrap_text(indent, indent, descr, 80));
135 text.push('\n');
136 text.push('\n');
137
138 text
139 }
140 }
141
142 fn dump_api_parameters(param: &ObjectSchema) -> String {
143
144 let mut res = wrap_text("", "", param.description, 80);
145
146 let mut required_list: Vec<String> = Vec::new();
147 let mut optional_list: Vec<String> = Vec::new();
148
149 for (prop, optional, schema) in param.properties {
150 let param_descr = get_property_description(
151 prop, &schema, ParameterDisplayStyle::Config, DocumentationFormat::ReST);
152
153 if *optional {
154 optional_list.push(param_descr);
155 } else {
156 required_list.push(param_descr);
157 }
158 }
159
160 if !required_list.is_empty() {
161
162 res.push_str("\n*Required properties:*\n\n");
163
164 for text in required_list {
165 res.push_str(&text);
166 res.push('\n');
167 }
168
169 }
170
171 if !optional_list.is_empty() {
172
173 res.push_str("\n*Optional properties:*\n\n");
174
175 for text in optional_list {
176 res.push_str(&text);
177 res.push('\n');
178 }
179 }
180
181 res
182 }
183
184 fn dump_api_return_schema(schema: &Schema) -> String {
185
186 let mut res = String::from("*Returns*: ");
187
188 let type_text = get_schema_type_text(schema, ParameterDisplayStyle::Config);
189 res.push_str(&format!("**{}**\n\n", type_text));
190
191 match schema {
192 Schema::Null => {
193 return res;
194 }
195 Schema::Boolean(schema) => {
196 let description = wrap_text("", "", schema.description, 80);
197 res.push_str(&description);
198 }
199 Schema::Integer(schema) => {
200 let description = wrap_text("", "", schema.description, 80);
201 res.push_str(&description);
202 }
203 Schema::String(schema) => {
204 let description = wrap_text("", "", schema.description, 80);
205 res.push_str(&description);
206 }
207 Schema::Array(schema) => {
208 let description = wrap_text("", "", schema.description, 80);
209 res.push_str(&description);
210 }
211 Schema::Object(obj_schema) => {
212 res.push_str(&dump_api_parameters(obj_schema));
213
214 }
215 }
216
217 res.push('\n');
218
219 res
220 }
221
222 fn dump_method_definition(method: &str, path: &str, def: Option<&ApiMethod>) -> Option<String> {
223
224 match def {
225 None => None,
226 Some(api_method) => {
227 let param_descr = dump_api_parameters(api_method.parameters);
228
229 let return_descr = dump_api_return_schema(api_method.returns);
230
231 let mut method = method;
232
233 if let ApiHandler::Async(_) = api_method.handler {
234 method = if method == "POST" { "UPLOAD" } else { method };
235 method = if method == "GET" { "DOWNLOAD" } else { method };
236 }
237
238 let res = format!("**{} {}**\n\n{}\n\n{}", method, path, param_descr, return_descr);
239 Some(res)
240 }
241 }
242 }
243
244 pub fn dump_api(output: &mut dyn Write, router: &Router, path: &str, mut pos: usize) -> Result<(), Error> {
245
246 let mut cond_print = |x| -> Result<_, Error> {
247 if let Some(text) = x {
248 if pos > 0 {
249 writeln!(output, "-----\n")?;
250 }
251 writeln!(output, "{}", text)?;
252 pos += 1;
253 }
254 Ok(())
255 };
256
257 cond_print(dump_method_definition("GET", path, router.get))?;
258 cond_print(dump_method_definition("POST", path, router.post))?;
259 cond_print(dump_method_definition("PUT", path, router.put))?;
260 cond_print(dump_method_definition("DELETE", path, router.delete))?;
261
262 match &router.subroute {
263 None => return Ok(()),
264 Some(SubRoute::MatchAll { router, param_name }) => {
265 let sub_path = if path == "." {
266 format!("<{}>", param_name)
267 } else {
268 format!("{}/<{}>", path, param_name)
269 };
270 dump_api(output, router, &sub_path, pos)?;
271 }
272 Some(SubRoute::Map(dirmap)) => {
273 //let mut keys: Vec<&String> = map.keys().collect();
274 //keys.sort_unstable_by(|a, b| a.cmp(b));
275 for (key, sub_router) in dirmap.iter() {
276 let sub_path = if path == "." { key.to_string() } else { format!("{}/{}", path, key) };
277 dump_api(output, sub_router, &sub_path, pos)?;
278 }
279 }
280 }
281
282 Ok(())
283 }